forked from eatvio/eatvio_app
Merge pull request 'main' (#31) from eatvio/eatvio_app:main into main
Reviewed-on: mso7nueh/eatvio_app#31
This commit is contained in:
3
assets/icons/star.svg
Normal file
3
assets/icons/star.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="29" viewBox="0 0 32 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.8652 10.0557C19.133 10.8796 19.9012 11.4374 20.7676 11.4375H30.041L22.5391 16.8887C21.8818 17.3662 21.5825 18.1896 21.7686 18.9697L21.8125 19.125L24.6777 27.9443L17.1758 22.4932C16.5186 22.0157 15.6434 21.9866 14.959 22.4043L14.8242 22.4932L7.32227 27.9443L10.1875 19.125C10.4385 18.3524 10.1967 17.5101 9.58789 16.9883L9.46094 16.8887L1.95898 11.4375H11.2324C12.0446 11.4374 12.77 10.9472 13.0781 10.207L13.1348 10.0557L16 1.23633L18.8652 10.0557Z" stroke="#2F80ED" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 602 B |
@@ -386,12 +386,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = "VL68C36HYS";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VL68C36HYS;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -411,8 +409,7 @@
|
||||
MARKETING_VERSION = 1.0.50;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Distribution";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Distribution;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -539,12 +536,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = "VL68C36HYS";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VL68C36HYS;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -564,8 +559,7 @@
|
||||
MARKETING_VERSION = 1.0.50;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Distribution";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Distribution;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -580,12 +574,10 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = "VL68C36HYS";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VL68C36HYS;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -605,8 +597,7 @@
|
||||
MARKETING_VERSION = 1.0.50;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Distribution";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Distribution;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -174,7 +174,10 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: CardSettingsScreen.path,
|
||||
name: CardSettingsScreen.name,
|
||||
builder: (context, state) => const CardSettingsScreen(),
|
||||
builder: (context, state) {
|
||||
final chosenPlanId = state.extra as int?;
|
||||
return CardSettingsScreen(chosenPlanId: chosenPlanId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/about-plan-screen',
|
||||
@@ -220,7 +223,8 @@ class AppRouter {
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final chatId = data['chatId'] as String;
|
||||
return ChatScreen(chatId: chatId);
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(chatId: chatId, sendInitialMessage: sendInitialMessage,);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_c
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||
@@ -96,11 +98,11 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
|
||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl()));
|
||||
sl.registerLazySingleton<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
|
||||
|
||||
// Cubit factories
|
||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
||||
@@ -112,12 +114,12 @@ Future<void> init() async {
|
||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl()));
|
||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||
sl.registerFactory<DiaryCubit>(() => DiaryCubit(diaryRepository: sl(), glossaryRepository: sl()));
|
||||
sl.registerFactory<DiaryCubit>(() => DiaryCubit(diaryRepository: sl(), glossaryRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
|
||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit());
|
||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
|
||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl()));
|
||||
@@ -158,6 +160,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<CardsRepository>(() => CardsRepository(cardsService: sl()));
|
||||
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
|
||||
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
|
||||
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
||||
|
||||
// Services
|
||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||
@@ -184,6 +187,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<CardsService>(() => CardsService(sl()));
|
||||
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
|
||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||
|
||||
// External
|
||||
sl.registerLazySingleton(
|
||||
|
||||
@@ -147,6 +147,8 @@ class PrimaryColors {
|
||||
|
||||
Color get greenA70001 => const Color(0XFF00BE13);
|
||||
|
||||
Color get green => const Color(0XFF0BB450);
|
||||
|
||||
// LightGreen
|
||||
Color get lightGreen300 => const Color(0XFFB2DF8A);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
||||
Future<void> showBottomSheetWithBlur({
|
||||
required BuildContext context,
|
||||
required Widget child,
|
||||
double height = 200,
|
||||
double? height = 200,
|
||||
bool hasBlur = true,
|
||||
bool enableDrag = true,
|
||||
bool userRootNavigator = false,
|
||||
|
||||
@@ -90,10 +90,16 @@ class CustomPopup extends StatelessWidget {
|
||||
addItem('assets/icons/edit_paint.svg', 'Редактировать', () {
|
||||
if (complainedContentType == ComplainedContent.posts) {
|
||||
GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel);
|
||||
} else {
|
||||
showInfoToast('В разработке');
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (complainedContentType == ComplainedContent.plan || complainedContentType == ComplainedContent.diet) {
|
||||
addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () {});
|
||||
addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () {
|
||||
showInfoToast('В разработке');
|
||||
return;
|
||||
});
|
||||
}
|
||||
addItem('assets/icons/red_trash.svg', 'Удалить', () async {
|
||||
if (complainedContentType == ComplainedContent.posts) {
|
||||
@@ -104,6 +110,9 @@ class CustomPopup extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
} else {
|
||||
showInfoToast('В разработке');
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -99,7 +99,7 @@ class CustomTextFormField extends StatelessWidget {
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
style: const TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w500),
|
||||
style: textStyle ?? const TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w500),
|
||||
obscureText: obscureText!,
|
||||
textInputAction: textInputAction,
|
||||
maxLines: maxLines,
|
||||
@@ -115,20 +115,17 @@ class CustomTextFormField extends StatelessWidget {
|
||||
prefixIconConstraints: prefixConstraints,
|
||||
suffixIcon: suffix,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
suffixStyle: textStyle ?? const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)),
|
||||
suffixStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)),
|
||||
suffixIconConstraints: suffixConstraints,
|
||||
isDense: false,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||
fillColor: const Color(0xFFF6F6F6),
|
||||
filled: true,
|
||||
border:
|
||||
borderDecoration ??
|
||||
border: borderDecoration ??
|
||||
OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none),
|
||||
enabledBorder:
|
||||
borderDecoration ??
|
||||
enabledBorder: borderDecoration ??
|
||||
OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none),
|
||||
focusedBorder:
|
||||
borderDecoration ??
|
||||
focusedBorder: borderDecoration ??
|
||||
OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none),
|
||||
),
|
||||
keyboardType: keyboardType,
|
||||
|
||||
@@ -7,9 +7,10 @@ import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class SuccessPublishedDietBottomSheet extends StatelessWidget {
|
||||
const SuccessPublishedDietBottomSheet({super.key, required this.title});
|
||||
class SuccessPublishedDietOrPlanBottomSheet extends StatelessWidget {
|
||||
const SuccessPublishedDietOrPlanBottomSheet({super.key, required this.title, this.isPlan = false});
|
||||
final String title;
|
||||
final bool isPlan;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
@@ -27,7 +28,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget {
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(
|
||||
'опубликованна',
|
||||
isPlan ? 'опубликован' : 'опубликованна',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(
|
||||
|
||||
14
lib/features/chat/models/plan_complain_model.dart
Normal file
14
lib/features/chat/models/plan_complain_model.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'plan_complain_model.freezed.dart';
|
||||
part 'plan_complain_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class PlanComplainModel with _$PlanComplainModel {
|
||||
|
||||
factory PlanComplainModel({
|
||||
@JsonKey(name: 'description') required String description,
|
||||
}) = _PlanComplainModel;
|
||||
|
||||
factory PlanComplainModel.fromJson(Map<String, dynamic> json) => _$PlanComplainModelFromJson(json);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||
@@ -72,7 +73,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
initChat(String chatId) async {
|
||||
initChat(String chatId, {bool sendInitialMessage = false}) async {
|
||||
try {
|
||||
final chatRoom = await chatRepository.getChatRoom(chatId);
|
||||
ChosenPlanModel? chosenPlan;
|
||||
@@ -96,6 +97,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
if (chatRoom.chosenPlan?.consultantId == chatRoom.users.receiver.id) {
|
||||
await checkCards();
|
||||
}
|
||||
|
||||
if (sendInitialMessage) {
|
||||
await chatRepository.sendMessage(chatId: chatId, data: 'Здравствуйте, хочу заказать у вас: ${chosenPlan?.plan.name ?? 'план'}');
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
typingSubscription?.cancel();
|
||||
@@ -205,6 +210,30 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> complainAboutConsultation({required String description}) async {
|
||||
try {
|
||||
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||
if (planId == null) return false;
|
||||
await chatRepository.complainAboutConsultation(id: planId, body: PlanComplainModel(description: description));
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> finishChosenPlan() async {
|
||||
try {
|
||||
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||
if (planId == null) return false;
|
||||
await chatRepository.finishChosenPlan(id: planId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
wsManager.unsubscribeDirectChatChannel();
|
||||
|
||||
@@ -15,6 +15,7 @@ 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/widgets/notification_banner.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/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
||||
@@ -28,7 +29,8 @@ import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
final String chatId;
|
||||
const ChatScreen({required this.chatId, super.key});
|
||||
final bool? sendInitialMessage;
|
||||
const ChatScreen({required this.chatId, super.key, required this.sendInitialMessage});
|
||||
|
||||
@override
|
||||
State<ChatScreen> createState() => _ChatScreenState();
|
||||
@@ -46,7 +48,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController.addListener(_onScroll);
|
||||
chatRoomCubit.initChat(widget.chatId);
|
||||
chatRoomCubit.initChat(widget.chatId, sendInitialMessage: widget.sendInitialMessage ?? false);
|
||||
}
|
||||
|
||||
_onScroll() {
|
||||
@@ -96,13 +98,26 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
BannerType? bannerType;
|
||||
if (chosenPlan == null) {
|
||||
bannerType = null;
|
||||
} else if (amIConsultant && state.hasCard) {
|
||||
} else if (amIConsultant && !state.hasCard) {
|
||||
bannerType = BannerType.card;
|
||||
} else if (amIConsultant && chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) {
|
||||
bannerType = BannerType.date;
|
||||
} else if (!amIConsultant &&
|
||||
(chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateStartDateHasBeenSet || chosenPlan.chosenPlanState == ChosenPlanStateEnum.statePendingPayment)) {
|
||||
bannerType = BannerType.payment;
|
||||
} else if (chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateDone) {
|
||||
bannerType = BannerType.collectCharge;
|
||||
} else if (!(chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateLastDayFinished) && !amIConsultant) {
|
||||
bannerType = BannerType.finishPlan;
|
||||
}
|
||||
|
||||
PacketOptions? chosenOption;
|
||||
if (chosenPlan?.optionType == 'options1') {
|
||||
chosenOption = chosenPlan?.plan.packet1Options;
|
||||
} else if (chosenPlan?.optionType == 'options2') {
|
||||
chosenOption = chosenPlan?.plan.packet2Options;
|
||||
} else if (chosenPlan?.optionType == 'options3') {
|
||||
chosenOption = chosenPlan?.plan.packet3Options;
|
||||
}
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
@@ -263,7 +278,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
itemCount: state.chatMessages.length + (state.isLoading ? 1 : 0),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (showUserDetailsButton) ...[
|
||||
if (!showUserDetailsButton) ...[
|
||||
// OrderCard(
|
||||
// plan: chosenPlan.plan,
|
||||
// isCustomerView: !amIConsultant,
|
||||
@@ -289,7 +304,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
),
|
||||
),
|
||||
if (bannerType != null)
|
||||
Padding(padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: NotificationBanner(bannerType: bannerType)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: NotificationBanner(
|
||||
bannerType: bannerType,
|
||||
chosenPlanId: chosenPlan?.id,
|
||||
planName: chosenPlan?.plan.name,
|
||||
planPrice: chosenOption?.price.toDouble(),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
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/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class CompletePlanBottomSheet extends StatefulWidget {
|
||||
const CompletePlanBottomSheet({super.key, required this.cubit});
|
||||
final ChatRoomCubit cubit;
|
||||
|
||||
@override
|
||||
State<CompletePlanBottomSheet> createState() => _CompletePlanBottomSheetState();
|
||||
}
|
||||
|
||||
class _CompletePlanBottomSheetState extends State<CompletePlanBottomSheet> {
|
||||
bool? isFailure;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
if (isFailure == true) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Произошла ошибка, попробуйте позже', style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomOutlinedButton(
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Подтверждение плана', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
'Вы уверенны что хотите подтвердить прохождения плана?',
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomOutlinedButton(
|
||||
text: s.confirm,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
final result = await widget.cubit.finishChosenPlan();
|
||||
if (result) {
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
isFailure = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
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_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.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/pages/payment_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chose_plan_date_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/complete_plan_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/plan_complain_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -13,8 +17,11 @@ import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class NotificationBanner extends StatefulWidget {
|
||||
const NotificationBanner({super.key, required this.bannerType});
|
||||
const NotificationBanner({super.key, required this.bannerType, this.planPrice, this.planName, this.chosenPlanId});
|
||||
final BannerType bannerType;
|
||||
final double? planPrice;
|
||||
final String? planName;
|
||||
final int? chosenPlanId;
|
||||
|
||||
@override
|
||||
State<NotificationBanner> createState() => _NotificationBannerState();
|
||||
@@ -31,96 +38,208 @@ class _NotificationBannerState extends State<NotificationBanner> {
|
||||
color: appTheme.gray50,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12, right: 8, left: 12),
|
||||
child: SvgPicture.asset('assets/icons/reversed_exclamation_mark.svg', height: 68, width: 68),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.bannerType != BannerType.collectCharge && widget.bannerType != BannerType.finishPlan)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12, right: 8, left: 12),
|
||||
child: SvgPicture.asset('assets/icons/reversed_exclamation_mark.svg', height: 68, width: 68),
|
||||
)
|
||||
else
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 12, right: 8, left: 12),
|
||||
child: _StarIcon(),
|
||||
),
|
||||
if (isLoading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 40, top: 24),
|
||||
child: Loader(),
|
||||
)
|
||||
else
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: switch (widget.bannerType) {
|
||||
BannerType.date => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text(s.choose_plan_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final cubit = context.read<ChatRoomCubit>();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ChosePlanDateWidget(
|
||||
chatCubit: cubit,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(s.choose_date, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
BannerType.card => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushReplacement(
|
||||
CardSettingsScreen.path,
|
||||
);
|
||||
},
|
||||
child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
BannerType.payment => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text(s.tap_on_link_to_pay, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final planId = (context.read<ChatRoomCubit>().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
final paymentLink = await context.read<ChatRoomCubit>().getPaymentLink();
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
if (paymentLink != null && planId != null) {
|
||||
if (!context.mounted) return;
|
||||
GoRouter.of(context).pushReplacement(
|
||||
PaymentScreen.path,
|
||||
extra: {'url': paymentLink, 'planId': planId},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
BannerType.collectCharge => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child:
|
||||
Text('${s.congratsYouHaveFinishedPlan} «${widget.planName}»', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text('${s.userConfirmedPlanCompletion} ${widget.planPrice}₽', style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
BannerType.finishPlan => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text('${s.congratsYouHaveFinishedPlan} «${widget.planName}». ${s.pleaseConfirmPlan}',
|
||||
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: size.width * 0.7,
|
||||
child: Text(s.planWillBeConfirmedAfterThreeDays, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isLoading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 40, top: 24),
|
||||
child: Loader(),
|
||||
)
|
||||
else
|
||||
if (widget.bannerType == BannerType.collectCharge)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: switch (widget.bannerType) {
|
||||
BannerType.date => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text(s.choose_plan_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final cubit = context.read<ChatRoomCubit>();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ChosePlanDateWidget(
|
||||
chatCubit: cubit,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(s.choose_date, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
BannerType.card => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text(s.can_not_choose_start_date, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushReplacement(
|
||||
CardSettingsScreen.path,
|
||||
);
|
||||
},
|
||||
child: Text(s.add_card, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
BannerType.payment => [
|
||||
SizedBox(
|
||||
width: size.width * 0.6,
|
||||
child: Text(s.tap_on_link_to_pay, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final planId = (context.read<ChatRoomCubit>().state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
final paymentLink = await context.read<ChatRoomCubit>().getPaymentLink();
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
if (paymentLink != null && planId != null) {
|
||||
if (!context.mounted) return;
|
||||
GoRouter.of(context).pushReplacement(
|
||||
PaymentScreen.path,
|
||||
extra: {'url': paymentLink, 'planId': planId},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: CustomOutlinedButton(
|
||||
text: s.collectCharge,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||
onPressed: () async {
|
||||
GoRouter.of(context).push(CardSettingsScreen.path, extra: widget.chosenPlanId);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.bannerType == BannerType.finishPlan) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: CustomOutlinedButton(
|
||||
text: s.confirm,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
final cubit = context.read<ChatRoomCubit>();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: CompletePlanBottomSheet(
|
||||
cubit: cubit,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SizedBox(
|
||||
child: Text(s.youCanComplain, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final cubit = context.read<ChatRoomCubit>();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
height: null,
|
||||
child: PlanComplainBottomSheet(
|
||||
cubit: cubit,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(s.leaveComplainRequest, style: TextStyles.textSMedium.copyWith(color: appTheme.red))),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum BannerType { date, card, payment }
|
||||
class _StarIcon extends StatelessWidget {
|
||||
const _StarIcon();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(color: appTheme.whiteA700, shape: BoxShape.circle),
|
||||
child: SvgPicture.asset('assets/icons/star.svg'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum BannerType { date, card, payment, collectCharge, finishPlan }
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
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/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class PlanComplainBottomSheet extends StatefulWidget {
|
||||
const PlanComplainBottomSheet({super.key, required this.cubit});
|
||||
final ChatRoomCubit cubit;
|
||||
|
||||
@override
|
||||
State<PlanComplainBottomSheet> createState() => _PlanComplainBottomSheetState();
|
||||
}
|
||||
|
||||
class _PlanComplainBottomSheetState extends State<PlanComplainBottomSheet> {
|
||||
final controller = TextEditingController();
|
||||
bool? result;
|
||||
int? textLength;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
if (result == true) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Подача жалобы', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text('Жалоба отправлена на рассмотрение. Обычно это занимает до трёх рабочих дней.', style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomOutlinedButton(
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (result == false) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Подача жалобы', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text('Произошла ошибка, попробуйте позже', style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomOutlinedButton(
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Подача жалобы', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Align(alignment: Alignment.centerLeft, child: Text('Опишите причину жалобы', style: TextStyles.textSMedium.copyWith(color: appTheme.black))),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomTextFormField(
|
||||
controller: controller,
|
||||
hasError: textLength != null && textLength! < 3,
|
||||
onChanged: (p0) {
|
||||
setState(() {
|
||||
textLength = p0.length;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (textLength != null && textLength! < 3) ...[
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomOutlinedButton(
|
||||
text: s.confirm,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
|
||||
onPressed: () async {
|
||||
if (controller.text.length < 3) {
|
||||
return;
|
||||
}
|
||||
final result = await widget.cubit.complainAboutConsultation(description: controller.text);
|
||||
setState(() {
|
||||
this.result = result;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ 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_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/payment_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
@@ -132,4 +133,25 @@ class ChatRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> finishChosenPlan({
|
||||
required int id,
|
||||
}) async {
|
||||
try {
|
||||
await chatService.finishChosenPlan(id: id);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> complainAboutConsultation({
|
||||
required int id,
|
||||
required PlanComplainModel body,
|
||||
}) async {
|
||||
try {
|
||||
await chatService.complainAboutConsultation(id: id, body: body);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/payment_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
@@ -68,4 +69,15 @@ abstract class ChatService {
|
||||
Future<ChosenPlanModel> getChosenPlan({
|
||||
@Path() required int id,
|
||||
});
|
||||
|
||||
@POST('/api/chosen_plans/{id}/confirm_finish')
|
||||
Future<void> finishChosenPlan({
|
||||
@Path() required int id,
|
||||
});
|
||||
|
||||
@POST('/api/chosen_plans/{id}/complain')
|
||||
Future<void> complainAboutConsultation({
|
||||
@Path() required int id,
|
||||
@Body() required PlanComplainModel body,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,8 +37,9 @@ class DiaryModel with _$DiaryModel {
|
||||
}
|
||||
|
||||
DateTime parseUnformattedDate(String date) {
|
||||
final dateChunks = date.split('/').map(int.parse).toList();
|
||||
return DateTime(dateChunks.last, dateChunks.first, dateChunks[1]);
|
||||
final dateChunks = date.split('-').map(int.parse).toList();
|
||||
|
||||
return DateTime(dateChunks.first, dateChunks[1], dateChunks.last);
|
||||
}
|
||||
|
||||
enum DiaryStateEnum {
|
||||
|
||||
@@ -8,7 +8,6 @@ part 'consultation_service.g.dart';
|
||||
abstract class ConsultationService {
|
||||
factory ConsultationService(Dio dio, {String baseUrl}) = _ConsultationService;
|
||||
|
||||
@GET('/api/consultations')
|
||||
@GET('/api/consultations/in_progress')
|
||||
Future<ConsultationPaginationModel> getConsultations();
|
||||
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedDietBottomSheet(
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(
|
||||
title: state.dietData.name ?? 'Диета',
|
||||
),
|
||||
);
|
||||
@@ -83,7 +83,7 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: SuccessPublishedDietBottomSheet(
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(
|
||||
title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
|
||||
),
|
||||
);
|
||||
@@ -193,8 +193,8 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
context.read<CreateDietCubit>().setDietState(DietState.draft);
|
||||
if (widget.diet != null) {
|
||||
final result = await context.read<CreateDietCubit>().updateDiet(
|
||||
widget.diet!.id,
|
||||
);
|
||||
widget.diet!.id,
|
||||
);
|
||||
if (result == 'error') {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}');
|
||||
|
||||
@@ -15,8 +15,8 @@ class CreatePlanModel with _$CreatePlanModel {
|
||||
const factory CreatePlanModel({
|
||||
String? name,
|
||||
String? details,
|
||||
String? state,
|
||||
int? duration,
|
||||
@Default('active') String? state,
|
||||
int? duration,
|
||||
required List<bool> type,
|
||||
XFile? image,
|
||||
required List<PacketOptions> options,
|
||||
@@ -25,9 +25,12 @@ class CreatePlanModel with _$CreatePlanModel {
|
||||
Future<Map<String, dynamic>> toCreationJson() async => {
|
||||
'name': name,
|
||||
'duration': duration,
|
||||
'state': state,
|
||||
'type': type,
|
||||
'state': 'active',
|
||||
'description_md': details,
|
||||
'type[slim]': type[0] ? '1' : '0',
|
||||
'type[fit]': type[1] ? '1' : '0',
|
||||
'type[wellness]': type[2] ? '1' : '0',
|
||||
'type[behavior]': type[3] ? '1' : '0',
|
||||
'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image!.path))).path, filename: image!.name) : null,
|
||||
};
|
||||
|
||||
|
||||
11
lib/features/create_plan/models/plan_slug_model.dart
Normal file
11
lib/features/create_plan/models/plan_slug_model.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'plan_slug_model.freezed.dart';
|
||||
part 'plan_slug_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class PlanSlugModel with _$PlanSlugModel {
|
||||
factory PlanSlugModel({@JsonKey(name: 'slug') required String slug}) = _PlanSlugModel;
|
||||
|
||||
factory PlanSlugModel.fromJson(Map<String, dynamic> json) => _$PlanSlugModelFromJson(json);
|
||||
}
|
||||
@@ -1,9 +1,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/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/success_published_diet_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/option_widget.dart';
|
||||
@@ -15,6 +19,7 @@ import 'package:eatvio_app/features/create_plan/presentation/widgets/select_exac
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class CreatePlanScreen extends StatelessWidget {
|
||||
static const name = 'createPlanScreen';
|
||||
@@ -36,177 +41,229 @@ class CreatePlanScreenContent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.adding_plan,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
MainBackgroundBouble(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.label,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: s.plan_label_desc,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setName(value);
|
||||
},
|
||||
maxLength: 150,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.plan_duration,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const PlanDurationDropDownWidget(),
|
||||
const SizedBox(height: 20),
|
||||
const PlanImagePickerWidget(),
|
||||
const SizedBox(height: 16),
|
||||
const PlanTypeChoiceWidget(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.plan_desc,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
height: 132,
|
||||
child: CustomTextFormField(
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
maxLength: 1000,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
expands: true,
|
||||
hintText: s.plan_desc_example,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setDetails(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
s.services,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionsCountButton(),
|
||||
const SizedBox(height: 20),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.selectedOptionIndex != current.selectedOptionIndex,
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
'${s.option} ${(state.selectedOptionIndex ?? 0) + 1}',
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionWidget(),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
CustomOutlinedButton(
|
||||
onPressed: () {},
|
||||
text: s.to_draft,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {},
|
||||
text: s.preview,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () {},
|
||||
text: s.publish,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.planCreated != current.planCreated || previous.isLoading != current.isLoading,
|
||||
builder: (context, state) {
|
||||
if (state.planCreated) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(
|
||||
title: state.planData.name ?? 'План',
|
||||
isPlan: true,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.adding_plan,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: 24.0),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SelectExactOptionWidget(),
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
MainBackgroundBouble(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.label,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.validationState != current.validationState,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
CustomTextFormField(
|
||||
hintText: s.plan_label_desc,
|
||||
hasError: state.validationState.titleHasError,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setName(value);
|
||||
},
|
||||
maxLength: 150,
|
||||
),
|
||||
if (state.validationState.titleHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.plan_duration,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const PlanDurationDropDownWidget(),
|
||||
const SizedBox(height: 20),
|
||||
const PlanImagePickerWidget(),
|
||||
const SizedBox(height: 16),
|
||||
const PlanTypeChoiceWidget(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
s.plan_desc,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.validationState != current.validationState,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 132,
|
||||
child: CustomTextFormField(
|
||||
hasError: state.validationState.descriptionHasError,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
maxLength: 1000,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
expands: true,
|
||||
hintText: s.plan_desc_example,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setDetails(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.validationState.descriptionHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
s.services,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionsCountButton(),
|
||||
const SizedBox(height: 20),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.selectedOptionIndex != current.selectedOptionIndex,
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
'${s.option} ${(state.selectedOptionIndex ?? 0) + 1}',
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.blueGray400),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const OptionWidget(),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// CustomOutlinedButton(
|
||||
// onPressed: () {},
|
||||
// text: s.to_draft,
|
||||
// height: 52,
|
||||
// buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
// buttonStyle: ButtonStyle(
|
||||
// side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
// backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 12),
|
||||
// CustomOutlinedButton(
|
||||
// onPressed: () async {},
|
||||
// text: s.preview,
|
||||
// height: 52,
|
||||
// buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray),
|
||||
// buttonStyle: ButtonStyle(
|
||||
// side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
// backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 12),
|
||||
state.isLoading
|
||||
? const Loader()
|
||||
: CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
await context.read<CreatePlanCubit>().publishPlan();
|
||||
},
|
||||
text: s.publish,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptionIndex == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: 24.0),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SelectExactOptionWidget(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,65 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
||||
class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||
CreatePlanCubit() : super(CreatePlanState.initial());
|
||||
final CreatePlanRepository createPlanRepository;
|
||||
CreatePlanCubit({required this.createPlanRepository}) : super(CreatePlanState.initial());
|
||||
|
||||
void setDetails(String text) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(details: text)));
|
||||
validateDetails();
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(name: name)));
|
||||
validateName();
|
||||
}
|
||||
|
||||
void setOptionTitle(String title) {
|
||||
final options = [...state.planData.options];
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(short: title);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(short: title);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionTitle();
|
||||
}
|
||||
|
||||
void setPrice(double price){
|
||||
void setPrice(double price) {
|
||||
final selectedOptionIndex = state.selectedOptionIndex ?? 0;
|
||||
final options = [...state.planData.options];
|
||||
options[selectedOptionIndex] = options[selectedOptionIndex].copyWith(price: price);
|
||||
final options = [...state.planData.options];
|
||||
options[selectedOptionIndex] = options[selectedOptionIndex].copyWith(price: price);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionPrice();
|
||||
}
|
||||
|
||||
void setOptionDescription(String description) {
|
||||
final options = [...state.planData.options];
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(description: description);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(description: description);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionDescription();
|
||||
}
|
||||
|
||||
void setOptionContent(String content, index) {
|
||||
final options = [...state.planData.options];
|
||||
void setOptionContent(String content, index) {
|
||||
final options = [...state.planData.options];
|
||||
final items = [...options[state.selectedOptionIndex ?? 0].items];
|
||||
if (items.length <= index) {
|
||||
items.add(content);
|
||||
}else {
|
||||
} else {
|
||||
items[index] = content;
|
||||
}
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: items);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: items);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionItems();
|
||||
}
|
||||
|
||||
void removeOptionContentItem(int index) {
|
||||
final options = [...state.planData.options];
|
||||
final updatedItems = [...options[state.selectedOptionIndex ?? 0].items];
|
||||
updatedItems.removeAt(index);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: updatedItems);
|
||||
options[state.selectedOptionIndex ?? 0] = options[state.selectedOptionIndex ?? 0].copyWith(items: updatedItems);
|
||||
emit(state.copyWith(planData: state.planData.copyWith(options: options)));
|
||||
validateSelectedOptionItems();
|
||||
}
|
||||
|
||||
void setDuration(int index) {
|
||||
@@ -62,10 +71,12 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||
),
|
||||
),
|
||||
);
|
||||
validateDuration();
|
||||
}
|
||||
|
||||
void setImage(XFile? image) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(image: image)));
|
||||
validateImage();
|
||||
}
|
||||
|
||||
void setPlanState(String planState) {
|
||||
@@ -74,9 +85,319 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
|
||||
|
||||
void setPlanType(List<bool> type) {
|
||||
emit(state.copyWith(planData: state.planData.copyWith(type: type)));
|
||||
if (type.any((elem) => elem)) {
|
||||
validateType();
|
||||
}
|
||||
}
|
||||
|
||||
void setSelectedOptionIndex(int? index) {
|
||||
emit(state.copyWith(selectedOptionIndex: index));
|
||||
}
|
||||
|
||||
bool validateName() {
|
||||
final name = state.planData.name;
|
||||
if (name == null || name.length < 3) {
|
||||
emit(state.copyWith.validationState(titleHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(titleHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateDetails() {
|
||||
final details = state.planData.details;
|
||||
if (details == null || details.length < 3) {
|
||||
emit(state.copyWith.validationState(descriptionHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(descriptionHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateDuration() {
|
||||
if (state.planData.duration == null) {
|
||||
emit(state.copyWith.validationState(durationHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(durationHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateType() {
|
||||
final type = state.planData.type;
|
||||
if (type.isEmpty || type.every((elem) => elem)) {
|
||||
emit(state.copyWith.validationState(typeHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(typeHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateImage() {
|
||||
if (state.planData.image == null) {
|
||||
emit(state.copyWith.validationState(imageHasError: true));
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith.validationState(imageHasError: false));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionPrice() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
if (option.price < 1200) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validatePriceOfAllFilledOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
if (options[0].price < 1200) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[1].price < 1200) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[2].price < 1200) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(priceHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(priceHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionItems() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
if (option.items.isEmpty) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(isItemsEmpty: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
final itemsErrors = Map<int, bool>.from(optionsValidationsStates[state.selectedOptionIndex ?? 0].itemsErrors);
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(isItemsEmpty: false);
|
||||
for (final (index, item) in option.items.indexed) {
|
||||
if (item.length < 3) {
|
||||
itemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
itemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(itemsErrors: itemsErrors);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateItemsOfAllFilledOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
|
||||
if (options[0].items.isEmpty) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(isItemsEmpty: true);
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(isItemsEmpty: false);
|
||||
}
|
||||
|
||||
final option1ItemsErrors = Map<int, bool>.from(optionsValidationsStates[0].itemsErrors);
|
||||
for (final (index, item) in options[0].items.indexed) {
|
||||
if (item.length < 3) {
|
||||
option1ItemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
option1ItemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(itemsErrors: option1ItemsErrors);
|
||||
if (state.selectedOptionIndex == null) {
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return result;
|
||||
}
|
||||
if (options[1].items.isEmpty) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(isItemsEmpty: true);
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(isItemsEmpty: false);
|
||||
}
|
||||
|
||||
final option2ItemsErrors = Map<int, bool>.from(optionsValidationsStates[1].itemsErrors);
|
||||
for (final (index, item) in options[1].items.indexed) {
|
||||
if (item.length < 3) {
|
||||
option2ItemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
option2ItemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(itemsErrors: option2ItemsErrors);
|
||||
|
||||
if (options[2].items.isEmpty) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(isItemsEmpty: true);
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(isItemsEmpty: false);
|
||||
}
|
||||
|
||||
final option3ItemsErrors = Map<int, bool>.from(optionsValidationsStates[2].itemsErrors);
|
||||
for (final (index, item) in options[2].items.indexed) {
|
||||
if (item.length < 3) {
|
||||
option3ItemsErrors[index] = true;
|
||||
result = false;
|
||||
} else {
|
||||
option3ItemsErrors[index] = false;
|
||||
}
|
||||
}
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(itemsErrors: option3ItemsErrors);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionTitle() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
if (option.short.length < 3) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateTitlesOfAllFilledOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
if (options[0].short.length < 3) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
if (state.selectedOptionIndex == null) return result;
|
||||
|
||||
if (options[1].short.length < 3) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[2].short.length < 3) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(titleHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(titleHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateSelectedOptionDescription() {
|
||||
final option = state.planData.options[state.selectedOptionIndex ?? 0];
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
if (option.description.length < 3) {
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return false;
|
||||
}
|
||||
optionsValidationsStates[state.selectedOptionIndex ?? 0] = optionsValidationsStates[state.selectedOptionIndex ?? 0].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateDescriptionsOfAllOptions() {
|
||||
final options = state.planData.options;
|
||||
final optionsValidationsStates = [...state.validationState.optionsValidationState];
|
||||
bool result = true;
|
||||
|
||||
if (options[0].description.length < 3) {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[0] = optionsValidationsStates[0].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
if (state.selectedOptionIndex == null) return result;
|
||||
if (options[1].description.length < 3) {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[1] = optionsValidationsStates[1].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
|
||||
if (options[2].description.length < 3) {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(descriptionHasError: true);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
result = false;
|
||||
} else {
|
||||
optionsValidationsStates[2] = optionsValidationsStates[2].copyWith(descriptionHasError: false);
|
||||
emit(state.copyWith.validationState(optionsValidationState: optionsValidationsStates));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validateAllFields() {
|
||||
final isNameValid = validateName();
|
||||
final isDetailsValid = validateDetails();
|
||||
final isImageValid = validateImage();
|
||||
final isDurationValid = validateDuration();
|
||||
final areTitlesValid = validateTitlesOfAllFilledOptions();
|
||||
final arePricesValid = validatePriceOfAllFilledOptions();
|
||||
final areDescriptionsValid = validateDescriptionsOfAllOptions();
|
||||
final areItemsValid = validateItemsOfAllFilledOptions();
|
||||
return isNameValid && isDetailsValid && isImageValid && isDurationValid && areTitlesValid && arePricesValid && areDescriptionsValid && areItemsValid;
|
||||
}
|
||||
|
||||
Future<void> publishPlan() async {
|
||||
if (validateAllFields()) {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final planModel = await state.planData.toCreationJson();
|
||||
final plan = await createPlanRepository.createPlan(model: planModel);
|
||||
if (state.planData.options[1].short.isNotEmpty) {
|
||||
final options = state.planData.toOptionsCreationJson();
|
||||
await createPlanRepository.createPacketOptions(slug: plan.slug, model: options);
|
||||
} else {
|
||||
final option = state.planData.toOptionCreationJson();
|
||||
await createPlanRepository.createPacketOption(slug: plan.slug, model: option);
|
||||
}
|
||||
emit(state.copyWith(isLoading: false, planCreated: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ class CreatePlanState with _$CreatePlanState {
|
||||
const factory CreatePlanState({
|
||||
required bool isLoading,
|
||||
required String? errorMessage,
|
||||
required CreatePlanModel planData,
|
||||
required CreatePlanModel planData,
|
||||
required PlanValidationState validationState,
|
||||
required bool planCreated,
|
||||
int? durationIndex,
|
||||
int? selectedOptionIndex,
|
||||
}) = _CreatePlanState;
|
||||
@@ -17,9 +19,55 @@ class CreatePlanState with _$CreatePlanState {
|
||||
factory CreatePlanState.initial() => CreatePlanState(
|
||||
isLoading: false,
|
||||
errorMessage: null,
|
||||
planCreated: false,
|
||||
validationState: PlanValidationState.empty(),
|
||||
planData: CreatePlanModel(
|
||||
type: [false, false, false, false],
|
||||
options: [PacketOptions.empty(), PacketOptions.empty(), PacketOptions.empty()],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PlanValidationState with _$PlanValidationState {
|
||||
factory PlanValidationState({
|
||||
required bool titleHasError,
|
||||
required bool durationHasError,
|
||||
required bool typeHasError,
|
||||
required bool descriptionHasError,
|
||||
required bool imageHasError,
|
||||
required List<OptionValidationState> optionsValidationState,
|
||||
}) = _PlanValidationState;
|
||||
|
||||
factory PlanValidationState.empty() => PlanValidationState(
|
||||
titleHasError: false,
|
||||
durationHasError: false,
|
||||
typeHasError: false,
|
||||
descriptionHasError: false,
|
||||
imageHasError: false,
|
||||
optionsValidationState: [
|
||||
OptionValidationState.empty(),
|
||||
OptionValidationState.empty(),
|
||||
OptionValidationState.empty(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class OptionValidationState with _$OptionValidationState {
|
||||
factory OptionValidationState({
|
||||
required bool titleHasError,
|
||||
required bool priceHasError,
|
||||
required bool descriptionHasError,
|
||||
required Map<int, bool> itemsErrors,
|
||||
required bool isItemsEmpty,
|
||||
}) = _OptionValidationState;
|
||||
|
||||
factory OptionValidationState.empty() => OptionValidationState(
|
||||
descriptionHasError: false,
|
||||
titleHasError: false,
|
||||
priceHasError: false,
|
||||
isItemsEmpty: false,
|
||||
itemsErrors: {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -39,34 +41,53 @@ class _AddItemWidgetState extends State<AddItemWidget> {
|
||||
if (widget.items.length > widget.index && controller.text != widget.items[widget.index]) {
|
||||
controller.text = widget.items[widget.index];
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
controller: controller,
|
||||
hintText: widget.enableHint ? s.option_content_hint_text : null,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setOptionContent(value, widget.index);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.canBeDeleted) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().removeOptionContentItem(widget.index);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.lightGray0,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 24),
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
buildWhen: (previous, current) => previous.validationState != current.validationState,
|
||||
builder: (context, state) {
|
||||
final hasError = state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].isItemsEmpty ||
|
||||
(state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].itemsErrors[widget.index] ?? false);
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
controller: controller,
|
||||
hasError: hasError,
|
||||
hintText: widget.enableHint ? s.option_content_hint_text : null,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setOptionContent(value, widget.index);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.canBeDeleted) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().removeOptionContentItem(widget.index);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.lightGray0,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].isItemsEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.addAtLeastOneItem)
|
||||
] else if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].itemsErrors[widget.index] ?? false) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols)
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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/core/widgets/main_background_bouble.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/widgets/add_item_widget.dart';
|
||||
@@ -32,35 +33,9 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
super.initState();
|
||||
customerPriceController.addListener(() {
|
||||
if (!customerPriceController.text.endsWith('₽')) {
|
||||
final textWithoutCurrency = customerPriceController.text.replaceAll('₽', '');
|
||||
customerPriceController.value = TextEditingValue(
|
||||
text: "$textWithoutCurrency₽",
|
||||
selection: TextSelection.collapsed(offset: textWithoutCurrency.length),
|
||||
);
|
||||
}
|
||||
final price = double.tryParse(customerPriceController.text.replaceAll('₽', ''));
|
||||
if (price != null && price < 1200) {
|
||||
customerPriceController.value = const TextEditingValue(
|
||||
text: "1200₽",
|
||||
selection: TextSelection.collapsed(offset: 4),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
creatorPriceController.addListener(() {
|
||||
if (!creatorPriceController.text.endsWith('₽')) {
|
||||
final textWithoutCurrency = creatorPriceController.text.replaceAll('₽', '');
|
||||
creatorPriceController.value = TextEditingValue(
|
||||
text: "$textWithoutCurrency₽",
|
||||
selection: TextSelection.collapsed(offset: textWithoutCurrency.length),
|
||||
);
|
||||
}
|
||||
final price = double.tryParse(creatorPriceController.text.replaceAll('₽', ''));
|
||||
if (price != null && price < 1000) {
|
||||
creatorPriceController.value = const TextEditingValue(
|
||||
text: "1000₽",
|
||||
selection: TextSelection.collapsed(offset: 4),
|
||||
);
|
||||
final text = customerPriceController.text.replaceAll('₽', '');
|
||||
customerPriceController.text = '$text₽';
|
||||
customerPriceController.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -94,12 +69,17 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: optionNameHintText,
|
||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].titleHasError,
|
||||
controller: optionTitleController,
|
||||
onChanged: (value) {
|
||||
context.read<CreatePlanCubit>().setOptionTitle(value);
|
||||
},
|
||||
maxLength: 150,
|
||||
),
|
||||
if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].titleHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
s.option_desc,
|
||||
@@ -114,6 +94,7 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
child: CustomTextFormField(
|
||||
controller: optionDescriptionController,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].descriptionHasError,
|
||||
maxLength: 1000,
|
||||
minLines: null,
|
||||
maxLines: null,
|
||||
@@ -124,6 +105,10 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].descriptionHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.atLeast3Symbols),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
@@ -141,14 +126,8 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: creatorPriceController,
|
||||
onChanged: (value) {
|
||||
double? price = double.tryParse(value.replaceAll('₽', ''));
|
||||
if (price != null) {
|
||||
price = price * 1.2;
|
||||
customerPriceController.text = price.toStringAsFixed(2);
|
||||
context.read<CreatePlanCubit>().setPrice(price);
|
||||
}
|
||||
},
|
||||
readOnly: true,
|
||||
textStyle: TextStyle(color: appTheme.gray),
|
||||
maxLength: 20,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -169,10 +148,11 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
CustomTextFormField(
|
||||
controller: customerPriceController,
|
||||
textInputType: const TextInputType.numberWithOptions(decimal: true),
|
||||
hasError: state.validationState.optionsValidationState[state.selectedOptionIndex ?? 0].priceHasError,
|
||||
onChanged: (value) {
|
||||
final price = double.tryParse(value.replaceAll('₽', ''));
|
||||
creatorPriceController.text = '${((price ?? 0) * 0.8).toStringAsFixed(2)}₽';
|
||||
if (price != null) {
|
||||
creatorPriceController.text = (price * 0.8).toStringAsFixed(2);
|
||||
context.read<CreatePlanCubit>().setPrice(price);
|
||||
}
|
||||
},
|
||||
@@ -235,33 +215,33 @@ class _OptionWidgetState extends State<OptionWidget> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const CustomDivider(),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isChecked,
|
||||
activeColor: appTheme.gray,
|
||||
checkColor: appTheme.gray,
|
||||
fillColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(Set<WidgetState> states) {
|
||||
return BorderSide(width: 1, color: appTheme.gray);
|
||||
},
|
||||
),
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
isChecked = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s.option_personal_diet_desc,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// const SizedBox(height: 16),
|
||||
// Row(
|
||||
// children: [
|
||||
// Checkbox(
|
||||
// value: isChecked,
|
||||
// activeColor: appTheme.gray,
|
||||
// checkColor: appTheme.gray,
|
||||
// fillColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
// side: WidgetStateBorderSide.resolveWith(
|
||||
// (Set<WidgetState> states) {
|
||||
// return BorderSide(width: 1, color: appTheme.gray);
|
||||
// },
|
||||
// ),
|
||||
// onChanged: (bool? value) {
|
||||
// setState(() {
|
||||
// isChecked = value ?? false;
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// s.option_personal_diet_desc,
|
||||
// style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ 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/custom_drop_down_button.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -36,28 +37,37 @@ class _PlanDurationDropDownWidgetState extends State<PlanDurationDropDownWidget>
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
return CustomDropDownButton(
|
||||
hint: Text(
|
||||
s.choose_plan_duration,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
context.read<CreatePlanCubit>().setDuration(durationDropDownTitles.indexOf(value));
|
||||
}
|
||||
},
|
||||
items: durationDropDownTitles
|
||||
.mapIndexed(
|
||||
(index, value) => DropdownMenuItem(
|
||||
value: durationDropDownTitles[index],
|
||||
child: Text(
|
||||
durationDropDownTitles[index],
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
|
||||
return Column(
|
||||
children: [
|
||||
CustomDropDownButton(
|
||||
hasError: state.validationState.durationHasError,
|
||||
hint: Text(
|
||||
s.choose_plan_duration,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
context.read<CreatePlanCubit>().setDuration(durationDropDownTitles.indexOf(value));
|
||||
}
|
||||
},
|
||||
items: durationDropDownTitles
|
||||
.mapIndexed(
|
||||
(index, value) => DropdownMenuItem(
|
||||
value: durationDropDownTitles[index],
|
||||
child: Text(
|
||||
durationDropDownTitles[index],
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0],
|
||||
),
|
||||
if (state.validationState.durationHasError) ...[
|
||||
const SizedBox(height: 4),
|
||||
ValidationErrorWidget(error: s.choose_plan_duration),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:dotted_border/dotted_border.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/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -72,74 +73,79 @@ class _PlanImagePickerWidgetState extends State<PlanImagePickerWidget> {
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showImagePickerOptions(s, context.read<CreatePlanCubit>());
|
||||
},
|
||||
child: Container(
|
||||
height: 240,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: state.planData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.planData.image?.path ?? ''))) : null,
|
||||
color: appTheme.gray50,
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_showImagePickerOptions(s, context.read<CreatePlanCubit>());
|
||||
},
|
||||
child: Container(
|
||||
height: 240,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: state.planData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.planData.image?.path ?? ''))) : null,
|
||||
color: appTheme.gray50,
|
||||
),
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
padding: state.planData.image != null ? null : const EdgeInsets.all(8),
|
||||
child: state.planData.image != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 6, right: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().setImage(null);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.dividerColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
DottedBorder(
|
||||
stackFit: StackFit.expand,
|
||||
dashPattern: const [14, 14],
|
||||
color: state.validationState.imageHasError ? appTheme.red : appTheme.dottedBorder,
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(12),
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/eatvio_logo.svg'),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
s.add_cover,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
padding: state.planData.image != null ? null : const EdgeInsets.all(8),
|
||||
child: state.planData.image != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 6, right: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreatePlanCubit>().setImage(null);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.dividerColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
DottedBorder(
|
||||
stackFit: StackFit.expand,
|
||||
dashPattern: const [14, 14],
|
||||
color: appTheme.dottedBorder,
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(12),
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
SvgPicture.asset('assets/icons/eatvio_logo.svg'),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
s.add_cover,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.gray),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.validationState.imageHasError) ...[const SizedBox(height: 8), ValidationErrorWidget(error: s.add_cover)],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
@@ -50,49 +51,55 @@ class _PlanTypeChoiceWidgetState extends State<PlanTypeChoiceWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<CreatePlanCubit, CreatePlanState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
return Column(
|
||||
children: [
|
||||
...List.generate(4, (index) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final planTypes = [...state.planData.type];
|
||||
planTypes[index] = !planTypes[index];
|
||||
context.read<CreatePlanCubit>().setPlanType(planTypes);
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: index == 3 ? 0 : 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: state.planData.type[index] ? appTheme.cardBackground : appTheme.whiteA700,
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: state.planData.type[index] ? appTheme.blueA400 : appTheme.lightGray00,
|
||||
Row(
|
||||
children: [
|
||||
...List.generate(4, (index) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final planTypes = [...state.planData.type];
|
||||
planTypes[index] = !planTypes[index];
|
||||
context.read<CreatePlanCubit>().setPlanType(planTypes);
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: index == 3 ? 0 : 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: state.planData.type[index] ? appTheme.cardBackground : appTheme.whiteA700,
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: state.planData.type[index] ? appTheme.blueA400 : appTheme.lightGray00,
|
||||
),
|
||||
),
|
||||
height: 104,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Image.asset(
|
||||
choiceContent[index].$1,
|
||||
height: 30,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
choiceContent[index].$2,
|
||||
overflow: TextOverflow.clip,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textFS10FW500.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
height: 104,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Image.asset(
|
||||
choiceContent[index].$1,
|
||||
height: 30,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
choiceContent[index].$2,
|
||||
overflow: TextOverflow.clip,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textFS10FW500.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
if (state.validationState.typeHasError) ...[const SizedBox(height: 8), ValidationErrorWidget(error: s.chosePlanType)]
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/create_plan/models/plan_slug_model.dart';
|
||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||
|
||||
class CreatePlanRepository {
|
||||
@@ -5,10 +7,11 @@ class CreatePlanRepository {
|
||||
|
||||
CreatePlanRepository({required this.createPlanService});
|
||||
|
||||
Future<void> createPlan({
|
||||
Future<PlanSlugModel> createPlan({
|
||||
required Map<String, dynamic> model,
|
||||
}) async {
|
||||
createPlanService.createPlan(model: model);
|
||||
final formData = FormData.fromMap(model);
|
||||
return await createPlanService.createPlan(formData: formData);
|
||||
}
|
||||
|
||||
Future<void> createPacketOption({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/create_plan/models/plan_slug_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'create_plan_service.g.dart';
|
||||
@@ -9,8 +10,8 @@ abstract class CreatePlanService {
|
||||
|
||||
@POST('/api/plans/create')
|
||||
@MultiPart()
|
||||
Future<void> createPlan({
|
||||
@Body() required Map<String, dynamic> model,
|
||||
Future<PlanSlugModel> createPlan({
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/plans/create/{slug}/packet_option')
|
||||
@@ -19,7 +20,7 @@ abstract class CreatePlanService {
|
||||
@Body() required Map<String, dynamic> model,
|
||||
});
|
||||
|
||||
@POST('/api/plans/create/{slug}/packet_options')
|
||||
@POST('/api/plans/create/{slug}/packet_options')
|
||||
Future<void> createPacketOptions({
|
||||
@Path() required String slug,
|
||||
@Body() required Map<String, dynamic> model,
|
||||
|
||||
@@ -36,6 +36,7 @@ class MealComment with _$MealComment {
|
||||
|
||||
factory MealComment({
|
||||
required String comment,
|
||||
required int typeNo,
|
||||
}) = _MealComment;
|
||||
|
||||
factory MealComment.fromJson(Map<String, dynamic> json) => _$MealCommentFromJson(json);
|
||||
@@ -45,7 +46,7 @@ class MealComment with _$MealComment {
|
||||
class PortionComment with _$PortionComment {
|
||||
|
||||
factory PortionComment({
|
||||
@JsonKey(name: 'commentable_id') required int portionId,
|
||||
@JsonKey(name: 'id') required int portionId,
|
||||
required String comment,
|
||||
}) = _PortionComment;
|
||||
|
||||
|
||||
813
lib/features/diary/models/consultant_review_model.freezed.dart
Normal file
813
lib/features/diary/models/consultant_review_model.freezed.dart
Normal file
@@ -0,0 +1,813 @@
|
||||
// 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 'consultant_review_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');
|
||||
|
||||
ConsultantReviewModel _$ConsultantReviewModelFromJson(
|
||||
Map<String, dynamic> json) {
|
||||
return _ConsultantReviewModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ConsultantReviewModel {
|
||||
String get planName => throw _privateConstructorUsedError;
|
||||
int get planDuration => throw _privateConstructorUsedError;
|
||||
int get currentPlanDay => throw _privateConstructorUsedError;
|
||||
ConsultantComments get consultantComments =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ConsultantReviewModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ConsultantReviewModelCopyWith<ConsultantReviewModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ConsultantReviewModelCopyWith<$Res> {
|
||||
factory $ConsultantReviewModelCopyWith(ConsultantReviewModel value,
|
||||
$Res Function(ConsultantReviewModel) then) =
|
||||
_$ConsultantReviewModelCopyWithImpl<$Res, ConsultantReviewModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String planName,
|
||||
int planDuration,
|
||||
int currentPlanDay,
|
||||
ConsultantComments consultantComments});
|
||||
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ConsultantReviewModelCopyWithImpl<$Res,
|
||||
$Val extends ConsultantReviewModel>
|
||||
implements $ConsultantReviewModelCopyWith<$Res> {
|
||||
_$ConsultantReviewModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? planName = null,
|
||||
Object? planDuration = null,
|
||||
Object? currentPlanDay = null,
|
||||
Object? consultantComments = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
planName: null == planName
|
||||
? _value.planName
|
||||
: planName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
planDuration: null == planDuration
|
||||
? _value.planDuration
|
||||
: planDuration // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
currentPlanDay: null == currentPlanDay
|
||||
? _value.currentPlanDay
|
||||
: currentPlanDay // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
consultantComments: null == consultantComments
|
||||
? _value.consultantComments
|
||||
: consultantComments // ignore: cast_nullable_to_non_nullable
|
||||
as ConsultantComments,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments {
|
||||
return $ConsultantCommentsCopyWith<$Res>(_value.consultantComments,
|
||||
(value) {
|
||||
return _then(_value.copyWith(consultantComments: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ConsultantReviewModelImplCopyWith<$Res>
|
||||
implements $ConsultantReviewModelCopyWith<$Res> {
|
||||
factory _$$ConsultantReviewModelImplCopyWith(
|
||||
_$ConsultantReviewModelImpl value,
|
||||
$Res Function(_$ConsultantReviewModelImpl) then) =
|
||||
__$$ConsultantReviewModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String planName,
|
||||
int planDuration,
|
||||
int currentPlanDay,
|
||||
ConsultantComments consultantComments});
|
||||
|
||||
@override
|
||||
$ConsultantCommentsCopyWith<$Res> get consultantComments;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ConsultantReviewModelImplCopyWithImpl<$Res>
|
||||
extends _$ConsultantReviewModelCopyWithImpl<$Res,
|
||||
_$ConsultantReviewModelImpl>
|
||||
implements _$$ConsultantReviewModelImplCopyWith<$Res> {
|
||||
__$$ConsultantReviewModelImplCopyWithImpl(_$ConsultantReviewModelImpl _value,
|
||||
$Res Function(_$ConsultantReviewModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? planName = null,
|
||||
Object? planDuration = null,
|
||||
Object? currentPlanDay = null,
|
||||
Object? consultantComments = null,
|
||||
}) {
|
||||
return _then(_$ConsultantReviewModelImpl(
|
||||
planName: null == planName
|
||||
? _value.planName
|
||||
: planName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
planDuration: null == planDuration
|
||||
? _value.planDuration
|
||||
: planDuration // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
currentPlanDay: null == currentPlanDay
|
||||
? _value.currentPlanDay
|
||||
: currentPlanDay // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
consultantComments: null == consultantComments
|
||||
? _value.consultantComments
|
||||
: consultantComments // ignore: cast_nullable_to_non_nullable
|
||||
as ConsultantComments,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ConsultantReviewModelImpl implements _ConsultantReviewModel {
|
||||
_$ConsultantReviewModelImpl(
|
||||
{required this.planName,
|
||||
required this.planDuration,
|
||||
required this.currentPlanDay,
|
||||
required this.consultantComments});
|
||||
|
||||
factory _$ConsultantReviewModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ConsultantReviewModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String planName;
|
||||
@override
|
||||
final int planDuration;
|
||||
@override
|
||||
final int currentPlanDay;
|
||||
@override
|
||||
final ConsultantComments consultantComments;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConsultantReviewModel(planName: $planName, planDuration: $planDuration, currentPlanDay: $currentPlanDay, consultantComments: $consultantComments)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ConsultantReviewModelImpl &&
|
||||
(identical(other.planName, planName) ||
|
||||
other.planName == planName) &&
|
||||
(identical(other.planDuration, planDuration) ||
|
||||
other.planDuration == planDuration) &&
|
||||
(identical(other.currentPlanDay, currentPlanDay) ||
|
||||
other.currentPlanDay == currentPlanDay) &&
|
||||
(identical(other.consultantComments, consultantComments) ||
|
||||
other.consultantComments == consultantComments));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, planName, planDuration, currentPlanDay, consultantComments);
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ConsultantReviewModelImplCopyWith<_$ConsultantReviewModelImpl>
|
||||
get copyWith => __$$ConsultantReviewModelImplCopyWithImpl<
|
||||
_$ConsultantReviewModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ConsultantReviewModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ConsultantReviewModel implements ConsultantReviewModel {
|
||||
factory _ConsultantReviewModel(
|
||||
{required final String planName,
|
||||
required final int planDuration,
|
||||
required final int currentPlanDay,
|
||||
required final ConsultantComments consultantComments}) =
|
||||
_$ConsultantReviewModelImpl;
|
||||
|
||||
factory _ConsultantReviewModel.fromJson(Map<String, dynamic> json) =
|
||||
_$ConsultantReviewModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get planName;
|
||||
@override
|
||||
int get planDuration;
|
||||
@override
|
||||
int get currentPlanDay;
|
||||
@override
|
||||
ConsultantComments get consultantComments;
|
||||
|
||||
/// Create a copy of ConsultantReviewModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ConsultantReviewModelImplCopyWith<_$ConsultantReviewModelImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ConsultantComments _$ConsultantCommentsFromJson(Map<String, dynamic> json) {
|
||||
return _ConsultantComments.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ConsultantComments {
|
||||
String? get dayComment => throw _privateConstructorUsedError;
|
||||
List<MealComment> get mealsComments => throw _privateConstructorUsedError;
|
||||
List<PortionComment> get portionsComments =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ConsultantComments to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ConsultantCommentsCopyWith<ConsultantComments> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ConsultantCommentsCopyWith<$Res> {
|
||||
factory $ConsultantCommentsCopyWith(
|
||||
ConsultantComments value, $Res Function(ConsultantComments) then) =
|
||||
_$ConsultantCommentsCopyWithImpl<$Res, ConsultantComments>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? dayComment,
|
||||
List<MealComment> mealsComments,
|
||||
List<PortionComment> portionsComments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ConsultantCommentsCopyWithImpl<$Res, $Val extends ConsultantComments>
|
||||
implements $ConsultantCommentsCopyWith<$Res> {
|
||||
_$ConsultantCommentsCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? dayComment = freezed,
|
||||
Object? mealsComments = null,
|
||||
Object? portionsComments = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
dayComment: freezed == dayComment
|
||||
? _value.dayComment
|
||||
: dayComment // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
mealsComments: null == mealsComments
|
||||
? _value.mealsComments
|
||||
: mealsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<MealComment>,
|
||||
portionsComments: null == portionsComments
|
||||
? _value.portionsComments
|
||||
: portionsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<PortionComment>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ConsultantCommentsImplCopyWith<$Res>
|
||||
implements $ConsultantCommentsCopyWith<$Res> {
|
||||
factory _$$ConsultantCommentsImplCopyWith(_$ConsultantCommentsImpl value,
|
||||
$Res Function(_$ConsultantCommentsImpl) then) =
|
||||
__$$ConsultantCommentsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? dayComment,
|
||||
List<MealComment> mealsComments,
|
||||
List<PortionComment> portionsComments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ConsultantCommentsImplCopyWithImpl<$Res>
|
||||
extends _$ConsultantCommentsCopyWithImpl<$Res, _$ConsultantCommentsImpl>
|
||||
implements _$$ConsultantCommentsImplCopyWith<$Res> {
|
||||
__$$ConsultantCommentsImplCopyWithImpl(_$ConsultantCommentsImpl _value,
|
||||
$Res Function(_$ConsultantCommentsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? dayComment = freezed,
|
||||
Object? mealsComments = null,
|
||||
Object? portionsComments = null,
|
||||
}) {
|
||||
return _then(_$ConsultantCommentsImpl(
|
||||
dayComment: freezed == dayComment
|
||||
? _value.dayComment
|
||||
: dayComment // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
mealsComments: null == mealsComments
|
||||
? _value._mealsComments
|
||||
: mealsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<MealComment>,
|
||||
portionsComments: null == portionsComments
|
||||
? _value._portionsComments
|
||||
: portionsComments // ignore: cast_nullable_to_non_nullable
|
||||
as List<PortionComment>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ConsultantCommentsImpl implements _ConsultantComments {
|
||||
_$ConsultantCommentsImpl(
|
||||
{required this.dayComment,
|
||||
required final List<MealComment> mealsComments,
|
||||
required final List<PortionComment> portionsComments})
|
||||
: _mealsComments = mealsComments,
|
||||
_portionsComments = portionsComments;
|
||||
|
||||
factory _$ConsultantCommentsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ConsultantCommentsImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String? dayComment;
|
||||
final List<MealComment> _mealsComments;
|
||||
@override
|
||||
List<MealComment> get mealsComments {
|
||||
if (_mealsComments is EqualUnmodifiableListView) return _mealsComments;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_mealsComments);
|
||||
}
|
||||
|
||||
final List<PortionComment> _portionsComments;
|
||||
@override
|
||||
List<PortionComment> get portionsComments {
|
||||
if (_portionsComments is EqualUnmodifiableListView)
|
||||
return _portionsComments;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_portionsComments);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConsultantComments(dayComment: $dayComment, mealsComments: $mealsComments, portionsComments: $portionsComments)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ConsultantCommentsImpl &&
|
||||
(identical(other.dayComment, dayComment) ||
|
||||
other.dayComment == dayComment) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._mealsComments, _mealsComments) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._portionsComments, _portionsComments));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
dayComment,
|
||||
const DeepCollectionEquality().hash(_mealsComments),
|
||||
const DeepCollectionEquality().hash(_portionsComments));
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ConsultantCommentsImplCopyWith<_$ConsultantCommentsImpl> get copyWith =>
|
||||
__$$ConsultantCommentsImplCopyWithImpl<_$ConsultantCommentsImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ConsultantCommentsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ConsultantComments implements ConsultantComments {
|
||||
factory _ConsultantComments(
|
||||
{required final String? dayComment,
|
||||
required final List<MealComment> mealsComments,
|
||||
required final List<PortionComment> portionsComments}) =
|
||||
_$ConsultantCommentsImpl;
|
||||
|
||||
factory _ConsultantComments.fromJson(Map<String, dynamic> json) =
|
||||
_$ConsultantCommentsImpl.fromJson;
|
||||
|
||||
@override
|
||||
String? get dayComment;
|
||||
@override
|
||||
List<MealComment> get mealsComments;
|
||||
@override
|
||||
List<PortionComment> get portionsComments;
|
||||
|
||||
/// Create a copy of ConsultantComments
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ConsultantCommentsImplCopyWith<_$ConsultantCommentsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
MealComment _$MealCommentFromJson(Map<String, dynamic> json) {
|
||||
return _MealComment.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$MealComment {
|
||||
String get comment => throw _privateConstructorUsedError;
|
||||
int get typeNo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this MealComment to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$MealCommentCopyWith<MealComment> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $MealCommentCopyWith<$Res> {
|
||||
factory $MealCommentCopyWith(
|
||||
MealComment value, $Res Function(MealComment) then) =
|
||||
_$MealCommentCopyWithImpl<$Res, MealComment>;
|
||||
@useResult
|
||||
$Res call({String comment, int typeNo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$MealCommentCopyWithImpl<$Res, $Val extends MealComment>
|
||||
implements $MealCommentCopyWith<$Res> {
|
||||
_$MealCommentCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? comment = null,
|
||||
Object? typeNo = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
comment: null == comment
|
||||
? _value.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
typeNo: null == typeNo
|
||||
? _value.typeNo
|
||||
: typeNo // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$MealCommentImplCopyWith<$Res>
|
||||
implements $MealCommentCopyWith<$Res> {
|
||||
factory _$$MealCommentImplCopyWith(
|
||||
_$MealCommentImpl value, $Res Function(_$MealCommentImpl) then) =
|
||||
__$$MealCommentImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String comment, int typeNo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$MealCommentImplCopyWithImpl<$Res>
|
||||
extends _$MealCommentCopyWithImpl<$Res, _$MealCommentImpl>
|
||||
implements _$$MealCommentImplCopyWith<$Res> {
|
||||
__$$MealCommentImplCopyWithImpl(
|
||||
_$MealCommentImpl _value, $Res Function(_$MealCommentImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? comment = null,
|
||||
Object? typeNo = null,
|
||||
}) {
|
||||
return _then(_$MealCommentImpl(
|
||||
comment: null == comment
|
||||
? _value.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
typeNo: null == typeNo
|
||||
? _value.typeNo
|
||||
: typeNo // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$MealCommentImpl implements _MealComment {
|
||||
_$MealCommentImpl({required this.comment, required this.typeNo});
|
||||
|
||||
factory _$MealCommentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$MealCommentImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String comment;
|
||||
@override
|
||||
final int typeNo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MealComment(comment: $comment, typeNo: $typeNo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MealCommentImpl &&
|
||||
(identical(other.comment, comment) || other.comment == comment) &&
|
||||
(identical(other.typeNo, typeNo) || other.typeNo == typeNo));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, comment, typeNo);
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MealCommentImplCopyWith<_$MealCommentImpl> get copyWith =>
|
||||
__$$MealCommentImplCopyWithImpl<_$MealCommentImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$MealCommentImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _MealComment implements MealComment {
|
||||
factory _MealComment(
|
||||
{required final String comment,
|
||||
required final int typeNo}) = _$MealCommentImpl;
|
||||
|
||||
factory _MealComment.fromJson(Map<String, dynamic> json) =
|
||||
_$MealCommentImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get comment;
|
||||
@override
|
||||
int get typeNo;
|
||||
|
||||
/// Create a copy of MealComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$MealCommentImplCopyWith<_$MealCommentImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
PortionComment _$PortionCommentFromJson(Map<String, dynamic> json) {
|
||||
return _PortionComment.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PortionComment {
|
||||
@JsonKey(name: 'id')
|
||||
int get portionId => throw _privateConstructorUsedError;
|
||||
String get comment => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this PortionComment to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$PortionCommentCopyWith<PortionComment> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $PortionCommentCopyWith<$Res> {
|
||||
factory $PortionCommentCopyWith(
|
||||
PortionComment value, $Res Function(PortionComment) then) =
|
||||
_$PortionCommentCopyWithImpl<$Res, PortionComment>;
|
||||
@useResult
|
||||
$Res call({@JsonKey(name: 'id') int portionId, String comment});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$PortionCommentCopyWithImpl<$Res, $Val extends PortionComment>
|
||||
implements $PortionCommentCopyWith<$Res> {
|
||||
_$PortionCommentCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? portionId = null,
|
||||
Object? comment = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
portionId: null == portionId
|
||||
? _value.portionId
|
||||
: portionId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
comment: null == comment
|
||||
? _value.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$PortionCommentImplCopyWith<$Res>
|
||||
implements $PortionCommentCopyWith<$Res> {
|
||||
factory _$$PortionCommentImplCopyWith(_$PortionCommentImpl value,
|
||||
$Res Function(_$PortionCommentImpl) then) =
|
||||
__$$PortionCommentImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({@JsonKey(name: 'id') int portionId, String comment});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$PortionCommentImplCopyWithImpl<$Res>
|
||||
extends _$PortionCommentCopyWithImpl<$Res, _$PortionCommentImpl>
|
||||
implements _$$PortionCommentImplCopyWith<$Res> {
|
||||
__$$PortionCommentImplCopyWithImpl(
|
||||
_$PortionCommentImpl _value, $Res Function(_$PortionCommentImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? portionId = null,
|
||||
Object? comment = null,
|
||||
}) {
|
||||
return _then(_$PortionCommentImpl(
|
||||
portionId: null == portionId
|
||||
? _value.portionId
|
||||
: portionId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
comment: null == comment
|
||||
? _value.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$PortionCommentImpl implements _PortionComment {
|
||||
_$PortionCommentImpl(
|
||||
{@JsonKey(name: 'id') required this.portionId, required this.comment});
|
||||
|
||||
factory _$PortionCommentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PortionCommentImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: 'id')
|
||||
final int portionId;
|
||||
@override
|
||||
final String comment;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PortionComment(portionId: $portionId, comment: $comment)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PortionCommentImpl &&
|
||||
(identical(other.portionId, portionId) ||
|
||||
other.portionId == portionId) &&
|
||||
(identical(other.comment, comment) || other.comment == comment));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, portionId, comment);
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$PortionCommentImplCopyWith<_$PortionCommentImpl> get copyWith =>
|
||||
__$$PortionCommentImplCopyWithImpl<_$PortionCommentImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$PortionCommentImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _PortionComment implements PortionComment {
|
||||
factory _PortionComment(
|
||||
{@JsonKey(name: 'id') required final int portionId,
|
||||
required final String comment}) = _$PortionCommentImpl;
|
||||
|
||||
factory _PortionComment.fromJson(Map<String, dynamic> json) =
|
||||
_$PortionCommentImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: 'id')
|
||||
int get portionId;
|
||||
@override
|
||||
String get comment;
|
||||
|
||||
/// Create a copy of PortionComment
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$PortionCommentImplCopyWith<_$PortionCommentImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
71
lib/features/diary/models/consultant_review_model.g.dart
Normal file
71
lib/features/diary/models/consultant_review_model.g.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'consultant_review_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ConsultantReviewModelImpl _$$ConsultantReviewModelImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$ConsultantReviewModelImpl(
|
||||
planName: json['planName'] as String,
|
||||
planDuration: (json['planDuration'] as num).toInt(),
|
||||
currentPlanDay: (json['currentPlanDay'] as num).toInt(),
|
||||
consultantComments: ConsultantComments.fromJson(
|
||||
json['consultantComments'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ConsultantReviewModelImplToJson(
|
||||
_$ConsultantReviewModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'planName': instance.planName,
|
||||
'planDuration': instance.planDuration,
|
||||
'currentPlanDay': instance.currentPlanDay,
|
||||
'consultantComments': instance.consultantComments,
|
||||
};
|
||||
|
||||
_$ConsultantCommentsImpl _$$ConsultantCommentsImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$ConsultantCommentsImpl(
|
||||
dayComment: json['dayComment'] as String?,
|
||||
mealsComments: (json['mealsComments'] as List<dynamic>)
|
||||
.map((e) => MealComment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
portionsComments: (json['portionsComments'] as List<dynamic>)
|
||||
.map((e) => PortionComment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ConsultantCommentsImplToJson(
|
||||
_$ConsultantCommentsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'dayComment': instance.dayComment,
|
||||
'mealsComments': instance.mealsComments,
|
||||
'portionsComments': instance.portionsComments,
|
||||
};
|
||||
|
||||
_$MealCommentImpl _$$MealCommentImplFromJson(Map<String, dynamic> json) =>
|
||||
_$MealCommentImpl(
|
||||
comment: json['comment'] as String,
|
||||
typeNo: (json['typeNo'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$MealCommentImplToJson(_$MealCommentImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'comment': instance.comment,
|
||||
'typeNo': instance.typeNo,
|
||||
};
|
||||
|
||||
_$PortionCommentImpl _$$PortionCommentImplFromJson(Map<String, dynamic> json) =>
|
||||
_$PortionCommentImpl(
|
||||
portionId: (json['id'] as num).toInt(),
|
||||
comment: json['comment'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PortionCommentImplToJson(
|
||||
_$PortionCommentImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.portionId,
|
||||
'comment': instance.comment,
|
||||
};
|
||||
@@ -23,7 +23,10 @@ enum PortionState {
|
||||
@freezed
|
||||
class DiaryDayWrapperModel with _$DiaryDayWrapperModel {
|
||||
const factory DiaryDayWrapperModel({
|
||||
@JsonKey(name: "diary") required DiaryDayModel diary,
|
||||
@JsonKey(
|
||||
name: "diary",
|
||||
)
|
||||
required DiaryDayModel diary,
|
||||
}) = _DiaryDayWrapperModel;
|
||||
|
||||
factory DiaryDayWrapperModel.fromJson(Map<String, dynamic> json) => _$DiaryDayWrapperModelFromJson(json);
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DiaryCubit extends Cubit<DiaryState> {
|
||||
@@ -19,8 +20,13 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
late int selectedMeal;
|
||||
final DiaryRepository diaryRepository;
|
||||
final GlossaryRepository glossaryRepository;
|
||||
final ProfileRepository profileRepository;
|
||||
int? diaryId;
|
||||
DiaryCubit({required this.diaryRepository, required this.glossaryRepository}) : super(const DiaryState.loading()) {
|
||||
DiaryCubit({
|
||||
required this.diaryRepository,
|
||||
required this.glossaryRepository,
|
||||
required this.profileRepository,
|
||||
}) : super(const DiaryState.loading()) {
|
||||
today = DateTime.now();
|
||||
selectedDay = today;
|
||||
diaryRepository.forceFetchStream.listen((_) {
|
||||
@@ -42,6 +48,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
|
||||
fetchDiary([bool needLoading = false, int? diaryId]) async {
|
||||
this.diaryId ??= diaryId;
|
||||
await profileRepository.getChosenDiets();
|
||||
if (needLoading) {
|
||||
emit(const DiaryState.loading());
|
||||
selectedMeal = 1;
|
||||
@@ -88,7 +95,12 @@ class DiaryCubit extends Cubit<DiaryState> {
|
||||
required String wakeUp,
|
||||
required String retire,
|
||||
}) async {
|
||||
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment);
|
||||
await diaryRepository.finishDiaryDay(
|
||||
diaryId: diaryId,
|
||||
wakeUp: wakeUp,
|
||||
retire: retire,
|
||||
comment: comment.isEmpty ? null : comment,
|
||||
);
|
||||
await fetchDiary();
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,12 @@ class DiaryScreenContent extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
if (diaryId == null) const CalendarWidget(),
|
||||
if (state is DiaryStateLoading) const Loader(),
|
||||
if (state is DiaryStateLoading) ...[
|
||||
const SizedBox(
|
||||
height: 60,
|
||||
),
|
||||
const Loader()
|
||||
],
|
||||
if (state is DiaryStateLoaded)
|
||||
WakeupAndRetireWidget(
|
||||
showRetire: state.diary.typeOfDiary.hasChosenPlan,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||
@@ -6,7 +7,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MealButtonWidget extends StatefulWidget {
|
||||
final int mealNumber;
|
||||
|
||||
const MealButtonWidget({
|
||||
super.key,
|
||||
required this.mealNumber,
|
||||
@@ -22,6 +22,18 @@ class _MealButtonWidgetState extends State<MealButtonWidget> {
|
||||
return BlocBuilder<DiaryCubit, DiaryState>(
|
||||
builder: (context, state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
final consultantComment = state.consultantReview?.consultantComments.mealsComments.firstWhereOrNull(
|
||||
(element) => element.typeNo == widget.mealNumber,
|
||||
);
|
||||
|
||||
final Color bgColor;
|
||||
if (state.selectedMeal == widget.mealNumber) {
|
||||
bgColor = appTheme.blueA400;
|
||||
} else if (consultantComment != null) {
|
||||
bgColor = appTheme.commentBg;
|
||||
} else {
|
||||
bgColor = const Color(0xFFF1F2F6);
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
|
||||
@@ -32,7 +44,7 @@ class _MealButtonWidgetState extends State<MealButtonWidget> {
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
color: state.selectedMeal == widget.mealNumber ? appTheme.blueA400 : const Color(0xFFF1F2F6),
|
||||
color: bgColor,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:collection/collection.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/diary/presentation/bloc/diary_cubit.dart';
|
||||
@@ -81,8 +82,10 @@ class _MealWidgetState extends State<MealWidget> {
|
||||
BlocBuilder<DiaryCubit, DiaryState>(
|
||||
builder: (context, state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
final mealsComments = state.consultantReview?.consultantComments.mealsComments;
|
||||
if (mealsComments != null && (state.selectedMeal - 1 < mealsComments.length)) {
|
||||
final consultantCommentOfMeal = state.consultantReview?.consultantComments.mealsComments.firstWhereOrNull(
|
||||
(element) => element.typeNo == state.selectedMeal,
|
||||
);
|
||||
if (consultantCommentOfMeal != null) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
@@ -90,7 +93,7 @@ class _MealWidgetState extends State<MealWidget> {
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CommentContainer(comment: mealsComments[state.selectedMeal - 1].comment),
|
||||
CommentContainer(comment: consultantCommentOfMeal.comment),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,13 @@ class _ProductWidgetState extends State<ProductWidget> {
|
||||
super.initState();
|
||||
diaryCubit = context.read<DiaryCubit>();
|
||||
portionName = widget.portion.portion == 'gr' ? 'гр' : 'шт';
|
||||
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ProductWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
portionName = widget.portion.portion == 'gr' ? 'гр' : 'шт';
|
||||
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
|
||||
}
|
||||
|
||||
@@ -64,8 +70,9 @@ class _ProductWidgetState extends State<ProductWidget> {
|
||||
state.diary.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser;
|
||||
final isDiaryFinished =
|
||||
state.diary.typeOfDiary.diaryState == DiaryStateEnum.finished || state.diary.typeOfDiary.diaryState == DiaryStateEnum.lastDayFinished;
|
||||
final canEditPortion = !state.isConsultantView && !isDiaryCommented && !isDiaryFinished;
|
||||
final canEditPortion = !state.isConsultantView && !isDiaryCommented && !isDiaryFinished && !widget.portion.fromDiet;
|
||||
final consultantComments = state.consultantReview?.consultantComments;
|
||||
final canMarkAsEaten = !state.isConsultantView && !isDiaryCommented && !isDiaryFinished;
|
||||
PortionComment? portionComment;
|
||||
if (consultantComments != null) {
|
||||
portionComment = consultantComments.portionsComments.firstWhereOrNull((elem) => elem.portionId == widget.portion.id);
|
||||
@@ -108,7 +115,7 @@ class _ProductWidgetState extends State<ProductWidget> {
|
||||
});
|
||||
},
|
||||
child: SvgPicture.asset('assets/icons/edit.svg'));
|
||||
} else if (!isDiaryCommented) {
|
||||
} else if (!isDiaryCommented && !widget.portion.fromDiet) {
|
||||
trailing = const Icon(Icons.more_vert_rounded);
|
||||
}
|
||||
return Column(
|
||||
@@ -121,24 +128,26 @@ class _ProductWidgetState extends State<ProductWidget> {
|
||||
scale: 1.2,
|
||||
child: Checkbox(
|
||||
value: isEaten,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
isEaten = value!;
|
||||
});
|
||||
if (isEaten) {
|
||||
diaryCubit.markEaten(
|
||||
diaryId: state.diary.id,
|
||||
mealId: state.selectedMeal,
|
||||
portionId: widget.portion.id,
|
||||
);
|
||||
} else {
|
||||
diaryCubit.unmarkEaten(
|
||||
diaryId: state.diary.id,
|
||||
mealId: state.selectedMeal,
|
||||
portionId: widget.portion.id,
|
||||
);
|
||||
}
|
||||
},
|
||||
onChanged: canMarkAsEaten
|
||||
? (value) {
|
||||
setState(() {
|
||||
isEaten = value!;
|
||||
});
|
||||
if (isEaten) {
|
||||
diaryCubit.markEaten(
|
||||
diaryId: state.diary.id,
|
||||
mealId: state.selectedMeal,
|
||||
portionId: widget.portion.id,
|
||||
);
|
||||
} else {
|
||||
diaryCubit.unmarkEaten(
|
||||
diaryId: state.diary.id,
|
||||
mealId: state.selectedMeal,
|
||||
portionId: widget.portion.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
activeColor: appTheme.blueA400,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||
|
||||
class DiaryRepository {
|
||||
final DiaryService diaryService;
|
||||
final StreamController<String> _diaryForceFetchController = StreamController<String>();
|
||||
final StreamController<String> _diaryForceFetchController = StreamController<String>.broadcast();
|
||||
|
||||
Stream<String> get forceFetchStream => _diaryForceFetchController.stream;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
@@ -317,31 +318,13 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
actions: [
|
||||
PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
path: 'diet/${widget.diet.id}',
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
id: widget.diet.id,
|
||||
),
|
||||
anchor: const Aligned(
|
||||
shiftToWithinBound: AxisFlag(x: true, y: true),
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
icon: SvgPicture.asset(
|
||||
'assets/icons/more_button.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
),
|
||||
CustomPopup(
|
||||
isAuthor: widget.diet.author.authorIsMe,
|
||||
path: 'diet/${widget.diet.id}',
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
id: widget.diet.id,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/core/models/pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'plan_pagination_model.freezed.dart';
|
||||
@@ -29,9 +30,9 @@ class PlanModel with _$PlanModel {
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson) required PlanType type,
|
||||
@JsonKey(name: "description_md") required String details,
|
||||
@JsonKey(name: "packets_count") required int packetsCount,
|
||||
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
|
||||
@JsonKey(name: "packet2_options") PacketOptions? packet2Options,
|
||||
@JsonKey(name: "packet3_options") PacketOptions? packet3Options,
|
||||
@JsonKey(name: "packet1_options", fromJson: parseOption) PacketOptions? packet1Options,
|
||||
@JsonKey(name: "packet2_options", fromJson: parseOption) PacketOptions? packet2Options,
|
||||
@JsonKey(name: "packet3_options", fromJson: parseOption) PacketOptions? packet3Options,
|
||||
@JsonKey(name: "author") required Author author,
|
||||
@JsonKey(name: "total_likes") required int totalLikes,
|
||||
@JsonKey(name: "total_views") required int totalViews,
|
||||
@@ -45,6 +46,13 @@ class PlanModel with _$PlanModel {
|
||||
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
|
||||
}
|
||||
|
||||
PacketOptions? parseOption(json) {
|
||||
if (json is Map<String, dynamic>) {
|
||||
return PacketOptions.fromJson(json);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
PlanType planTypeFromJson(dynamic json) {
|
||||
if (json is List<dynamic>) {
|
||||
return const PlanType(fit: false, slim: false, behavior: false, wellness: false);
|
||||
@@ -70,10 +78,10 @@ class PacketOptions with _$PacketOptions {
|
||||
class PlanType with _$PlanType {
|
||||
const PlanType._();
|
||||
const factory PlanType({
|
||||
@JsonKey(name: "fit") required bool? fit,
|
||||
@JsonKey(name: "slim") required bool? slim,
|
||||
@JsonKey(name: "behavior") required bool? behavior,
|
||||
@JsonKey(name: "wellness") required bool? wellness,
|
||||
@JsonKey(name: "fit", fromJson: parseBool) required bool? fit,
|
||||
@JsonKey(name: "slim", fromJson: parseBool) required bool? slim,
|
||||
@JsonKey(name: "behavior", fromJson: parseBool) required bool? behavior,
|
||||
@JsonKey(name: "wellness", fromJson: parseBool) required bool? wellness,
|
||||
}) = _PlanType;
|
||||
|
||||
factory PlanType.fromJson(Map<String, dynamic> json) => _$PlanTypeFromJson(json);
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
@@ -13,7 +13,6 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -83,28 +82,17 @@ class _DietsWidgetState extends State<DietsWidget> {
|
||||
Positioned(
|
||||
right: 12,
|
||||
top: 12,
|
||||
child: PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
path: 'diet/${widget.diet.id}',
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
id: widget.diet.id,
|
||||
),
|
||||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 24,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: const Icon(Icons.more_horiz),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 24,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: CustomPopup(
|
||||
isAuthor: widget.diet.author.authorIsMe,
|
||||
path: 'diet/${widget.diet.id}',
|
||||
id: widget.diet.id,
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,10 +3,10 @@ import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.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/core/theme/custom_text_style.dart';
|
||||
@@ -16,7 +16,6 @@ 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';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -24,6 +23,7 @@ import 'package:share_plus/share_plus.dart';
|
||||
class PlanWidget extends StatefulWidget {
|
||||
final PlanModel plan;
|
||||
final int index;
|
||||
|
||||
const PlanWidget({
|
||||
super.key,
|
||||
required this.plan,
|
||||
@@ -87,28 +87,18 @@ class _PlanWidgetState extends State<PlanWidget> {
|
||||
Positioned(
|
||||
right: 12,
|
||||
top: 12,
|
||||
child: PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
path: 'plan/${widget.plan.id}',
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
id: widget.plan.id,
|
||||
),
|
||||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 24,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: const Icon(Icons.more_horiz),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 24,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: CustomPopup(
|
||||
isAuthor: widget.plan.author.authorIsMe,
|
||||
path: 'plan/${widget.plan.id}',
|
||||
id: widget.plan.id,
|
||||
slug: widget.plan.slug,
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
@@ -16,7 +16,6 @@ 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';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -113,31 +112,21 @@ class _RecipesWidgetState extends State<RecipesWidget> {
|
||||
size: 19,
|
||||
),
|
||||
),
|
||||
// Testing Issue 329
|
||||
Positioned(
|
||||
right: 12,
|
||||
top: 12,
|
||||
child: PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
slug: widget.recipe.slug,
|
||||
),
|
||||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 24,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: const Icon(Icons.more_horiz),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 24,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: CustomPopup(
|
||||
isAuthor: widget.recipe.author.authorIsMe,
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
slug: widget.recipe.slug,
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -68,7 +68,7 @@ class _InternetConnectionCheckerScreenState extends State<InternetConnectionChec
|
||||
final uris = [
|
||||
Uri.parse('https://google.com'),
|
||||
Uri.parse('https://yandex.ru'),
|
||||
Uri.parse('https://dev2.eatvqqwewqeio.com'),
|
||||
Uri.parse('https://dev2.eatvio.com'),
|
||||
Uri.parse('https://eatvio.ru'),
|
||||
];
|
||||
for (final uri in uris) {
|
||||
|
||||
@@ -118,7 +118,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
if (!context.mounted) return;
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: SuccessPublishedDietBottomSheet(title: widget.diet.name),
|
||||
child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name),
|
||||
);
|
||||
await context.read<MyDietsCubit>().loadAllDiets();
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,6 @@ part 'card_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class CardModel with _$CardModel {
|
||||
|
||||
factory CardModel({
|
||||
required int id,
|
||||
@JsonKey(name: 'bank_pan') required String cardNumber,
|
||||
@@ -16,7 +15,7 @@ class CardModel with _$CardModel {
|
||||
}
|
||||
|
||||
bool parseBool(json) {
|
||||
if (json == 1 || json == true) {
|
||||
if (json == 1 || json == true || json == '1') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -28,20 +28,19 @@ class UserCardsCubit extends Cubit<UserCardsState> {
|
||||
emit(state.copyWith(cards: cards, state: BaseState.success));
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(state.copyWith(state: BaseState.error, error: e.toString()));
|
||||
emit(state.copyWith(state: BaseState.error, error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markAsDefault({required int id}) async {
|
||||
final oldCards = [...state.cards];
|
||||
if (isClosed) return;
|
||||
final cards =
|
||||
oldCards.map((card) {
|
||||
if (card.id == id) {
|
||||
return card.copyWith(isDefault: true);
|
||||
}
|
||||
return card.copyWith(isDefault: false);
|
||||
}).toList();
|
||||
final cards = oldCards.map((card) {
|
||||
if (card.id == id) {
|
||||
return card.copyWith(isDefault: true);
|
||||
}
|
||||
return card.copyWith(isDefault: false);
|
||||
}).toList();
|
||||
emit(state.copyWith(cards: cards));
|
||||
|
||||
EasyDebounce.debounce('markAsDefault', Duration(milliseconds: id), () async {
|
||||
@@ -66,4 +65,14 @@ class UserCardsCubit extends Cubit<UserCardsState> {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> collectCharge({required int chosenPlaId}) async {
|
||||
try {
|
||||
await cardsRepository.collectCharge(chosenPlaId: chosenPlaId);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/enums/base_state.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/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
|
||||
@@ -11,104 +15,221 @@ import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class CardSettingsScreen extends StatelessWidget {
|
||||
static const path = '/card-settings-screen';
|
||||
static const name = 'cardSettingsScreen';
|
||||
const CardSettingsScreen({super.key});
|
||||
|
||||
const CardSettingsScreen({super.key, this.chosenPlanId});
|
||||
final int? chosenPlanId;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(create: (context) => sl<UserCardsCubit>()..getCards(), child: const CardsSettingsContent());
|
||||
return BlocProvider(
|
||||
create: (context) => sl<UserCardsCubit>()..getCards(),
|
||||
child: CardsSettingsContent(
|
||||
chosenPlanId: chosenPlanId,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class CardsSettingsContent extends StatelessWidget {
|
||||
const CardsSettingsContent({super.key});
|
||||
const CardsSettingsContent({super.key, this.chosenPlanId});
|
||||
final int? chosenPlanId;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF0F2F5),
|
||||
appBar: AppBar(
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
|
||||
title: Text(s.my_cards),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text('Привязанные карты', style: TextStyle(color: Color(0xFF323F4B), fontSize: 18, fontWeight: FontWeight.w600, height: 1)),
|
||||
return BlocBuilder<UserCardsCubit, UserCardsState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF0F2F5),
|
||||
appBar: AppBar(
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))),
|
||||
title: Text(s.my_cards),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.white),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text('Привязанные карты', style: TextStyle(color: Color(0xFF323F4B), fontSize: 18, fontWeight: FontWeight.w600, height: 1)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final defaultCardNumber = state.cards.firstWhereOrNull((element) => element.isDefault)?.cardNumber;
|
||||
if (defaultCardNumber != null) {
|
||||
return Text(
|
||||
'Выплату будут производиться\nна карту ${defaultCardNumber.substring(defaultCardNumber.length - 7)}',
|
||||
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500, height: 1),
|
||||
);
|
||||
}
|
||||
return Text(s.no_default_cards, style: TextStyles.textMMedium.copyWith(color: appTheme.black));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
switch (state.state) {
|
||||
case BaseState.error:
|
||||
return SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: Center(
|
||||
child: DataErrorWidget(
|
||||
onReload: () async {
|
||||
context.read<UserCardsCubit>().getCards();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
case BaseState.success:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: List.generate(state.cards.length + 1, (index) {
|
||||
if (index < state.cards.length) {
|
||||
final cardsNumber = state.cards[index].cardNumber.substring(state.cards[index].cardNumber.length - 7);
|
||||
return CardWidget(isSelected: state.cards[index].isDefault, cardId: state.cards[index].id, cardNumber: cardsNumber);
|
||||
}
|
||||
return const CardWidget(enabledAddCard: true);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return SizedBox(width: MediaQuery.sizeOf(context).width, height: 200, child: const Center(child: Loader()));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
),
|
||||
if (chosenPlanId != null && state.cards.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: BlocBuilder<UserCardsCubit, UserCardsState>(
|
||||
builder: (context, state) {
|
||||
child: CustomOutlinedButton(
|
||||
text: s.collectCharge,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||
onPressed: () async {
|
||||
final defaultCardNumber = state.cards.firstWhereOrNull((element) => element.isDefault)?.cardNumber;
|
||||
if (defaultCardNumber != null) {
|
||||
return Text(
|
||||
'Выплату будут производиться\nна карту ${defaultCardNumber.substring(defaultCardNumber.length - 7)}',
|
||||
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500, height: 1),
|
||||
final result = await context.read<UserCardsCubit>().collectCharge(chosenPlaId: chosenPlanId!);
|
||||
if (context.mounted) {
|
||||
if (!result) {
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
s.chargeCollectionFailed,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
s.contactSupport,
|
||||
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
|
||||
),
|
||||
const 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: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const BottomSheetHandlerBar(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
s.chargeApproved,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
'${s.moneySentToCard} ${defaultCardNumber?.substring(defaultCardNumber.length - 7)}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
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: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Text(s.no_default_cards, style: TextStyles.textMMedium.copyWith(color: appTheme.black));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: BlocBuilder<UserCardsCubit, UserCardsState>(
|
||||
builder: (context, state) {
|
||||
switch (state.state) {
|
||||
case BaseState.error:
|
||||
return SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: Center(
|
||||
child: DataErrorWidget(
|
||||
onReload: () async {
|
||||
context.read<UserCardsCubit>().getCards();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
case BaseState.success:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: List.generate(state.cards.length + 1, (index) {
|
||||
if (index < state.cards.length) {
|
||||
final cardsNumber = state.cards[index].cardNumber.substring(state.cards[index].cardNumber.length - 7);
|
||||
return CardWidget(isSelected: state.cards[index].isDefault, cardId: state.cards[index].id, cardNumber: cardsNumber);
|
||||
}
|
||||
return const CardWidget(enabledAddCard: true);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return SizedBox(width: MediaQuery.sizeOf(context).width, height: 200, child: const Center(child: Loader()));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,12 @@ class CardsRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> collectCharge({required int chosenPlaId}) async {
|
||||
try {
|
||||
await cardsService.collectCharge(chosenPlanId: chosenPlaId);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,10 @@ abstract class CardsService {
|
||||
Future<void> deleteCard({
|
||||
@Path('id') required int id,
|
||||
});
|
||||
|
||||
@POST('/api/chosen_plans/{id}/take_payin')
|
||||
Future<void> collectCharge({
|
||||
@Path('id') required int chosenPlanId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/collect_charge_banner.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/payment_banner.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_info.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_props.dart';
|
||||
@@ -61,7 +62,7 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
|
||||
builder: (context, state) {
|
||||
final plan = state.chosenPlan;
|
||||
if (state.isLoading) {
|
||||
Scaffold(
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
s.infoAboutOrder,
|
||||
@@ -113,7 +114,7 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
|
||||
planStatus = PlanStatusValues.paid;
|
||||
} else if (widget.successfulPayment == false) {
|
||||
planStatus = PlanStatusValues.paymentFailed;
|
||||
} else if (plan.chosenPlanState == ChosenPlanStateEnum.statePaid) {
|
||||
} else if (plan.chosenPlanState.isStateAfterPaidState()) {
|
||||
planStatus = PlanStatusValues.paid;
|
||||
} else {
|
||||
planStatus = PlanStatusValues.pendingPayment;
|
||||
@@ -134,6 +135,12 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
if (plan.chosenPlanState == ChosenPlanStateEnum.stateDone) ...[
|
||||
CollectChargeBanner(
|
||||
chosenPlan: plan,
|
||||
price: chosenOption?.price ?? 0,
|
||||
)
|
||||
],
|
||||
if (widget.successfulPayment != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
PaymentBanner(
|
||||
@@ -148,7 +155,10 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
PlanProps(planDuration: plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: planStatus),
|
||||
PlanProps(
|
||||
planDuration: plan.plan.duration,
|
||||
planPrice: chosenOption?.price.toDouble() ?? 0,
|
||||
planStatus: planStatus),
|
||||
const SizedBox(height: 8),
|
||||
PlanInfo(
|
||||
title: plan.plan.name,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
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/main_background_bouble.dart';
|
||||
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class CollectChargeBanner extends StatelessWidget {
|
||||
const CollectChargeBanner({super.key, required this.chosenPlan, required this.price});
|
||||
final ChosenPlanModel chosenPlan;
|
||||
final double price;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return MainBackgroundBouble(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400, borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20))),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 80),
|
||||
child: Text(
|
||||
'${s.congratsYouHaveFinishedPlan} «${chosenPlan.plan.name}»',
|
||||
style: TextStyles.textMSemibold.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 80),
|
||||
child: Text(
|
||||
'${s.userConfirmedPlanCompletion} $price₽',
|
||||
style: TextStyles.textSMedium.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CustomOutlinedButton(
|
||||
text: s.collectCharge,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||
onPressed: () async {
|
||||
GoRouter.of(context).push(CardSettingsScreen.path, extra: chosenPlan.id);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ class PaymentBanner extends StatefulWidget {
|
||||
const PaymentBanner({
|
||||
super.key,
|
||||
required this.successfulPayment,
|
||||
|
||||
});
|
||||
final bool successfulPayment;
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ enum ChosenPlanStateEnum {
|
||||
|
||||
bool isStateAfterPaidState() {
|
||||
switch (this) {
|
||||
case ChosenPlanStateEnum.stateCanceledByTheInitiator:
|
||||
case ChosenPlanStateEnum.stateInited:
|
||||
case ChosenPlanStateEnum.stateInCoordination:
|
||||
case ChosenPlanStateEnum.stateStartDateHasBeenSet:
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
@@ -112,31 +113,13 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
actions: [
|
||||
PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
path: 'plan/${widget.plan.id}',
|
||||
slug: widget.plan.slug,
|
||||
),
|
||||
anchor: const Aligned(
|
||||
shiftToWithinBound: AxisFlag(x: true, y: true),
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
icon: SvgPicture.asset(
|
||||
'assets/icons/more_button.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
),
|
||||
CustomPopup(
|
||||
isAuthor: widget.plan.author.authorIsMe,
|
||||
path: 'plan/${widget.plan.id}',
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
slug: widget.plan.slug,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: const Loader(),
|
||||
@@ -237,6 +220,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': chosenPlan.chatId,
|
||||
'sendInitialMessage': true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
@@ -54,6 +55,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
final textEditingController = TextEditingController();
|
||||
bool isMoreOptionsVisible = false;
|
||||
bool _jumped = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -142,8 +144,10 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1.2),
|
||||
decoration: InputDecoration(
|
||||
isDense: false, // убираем true, чтобы padding учитывался корректно
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен
|
||||
isDense: false,
|
||||
// убираем true, чтобы padding учитывался корректно
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
// вертикальный паддинг увеличен
|
||||
hintText: 'Комментарий',
|
||||
hintStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 1.2, color: Color(0xFFAEAEB2)),
|
||||
filled: true,
|
||||
@@ -211,23 +215,13 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
actions: [
|
||||
PortalTarget(
|
||||
visible: isMoreOptionsVisible,
|
||||
portalFollower: MoreOptions(
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
slug: widget.recipe.slug,
|
||||
),
|
||||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||||
});
|
||||
},
|
||||
icon: SvgPicture.asset('assets/icons/more_button.svg', width: 20, height: 20),
|
||||
),
|
||||
CustomPopup(
|
||||
isAuthor: widget.recipe.author.authorIsMe,
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
slug: widget.recipe.slug,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -17,6 +19,7 @@ class SupportScreen extends StatelessWidget {
|
||||
title: const Text('Поддержка'),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
@@ -85,6 +88,60 @@ class SupportScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const CustomDivider(),
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Правовые документы',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/public_offer.pdf')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
'Публичная офёрта',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
'Политика конфиденциальности',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () async => await launchUrl(Uri.parse('$baseUrl/company-details')),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
'О компании',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: 80),
|
||||
SvgPicture.asset('assets/images/logo_welcome.svg'),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
@@ -70,7 +70,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1,
|
||||
height: 1.15,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -33,6 +33,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
|
||||
"addAtLeastOneItem": MessageLookupByLibrary.simpleMessage(
|
||||
"Добавьте хотя бы одину опцию",
|
||||
),
|
||||
"add_card": MessageLookupByLibrary.simpleMessage("Привязать карту"),
|
||||
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
|
||||
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
|
||||
@@ -55,6 +58,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"another_form_of_stage": MessageLookupByLibrary.simpleMessage("этапа"),
|
||||
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
|
||||
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
|
||||
"atLeast3Symbols": MessageLookupByLibrary.simpleMessage(
|
||||
"Минимум 3 символа",
|
||||
),
|
||||
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
|
||||
"average_pressure": MessageLookupByLibrary.simpleMessage(
|
||||
"Средние показатели давления:",
|
||||
@@ -86,6 +92,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
|
||||
),
|
||||
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
|
||||
"chargeApproved": MessageLookupByLibrary.simpleMessage("Выплата одобрена"),
|
||||
"chargeCollectionFailed": MessageLookupByLibrary.simpleMessage(
|
||||
"Не удалось оформить выплату",
|
||||
),
|
||||
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
|
||||
"check_out_video_recipe": MessageLookupByLibrary.simpleMessage(
|
||||
"Посмотреть видео рецепт",
|
||||
@@ -105,6 +115,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
|
||||
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике",
|
||||
),
|
||||
"chosePlanType": MessageLookupByLibrary.simpleMessage("Выберите тип плана"),
|
||||
"chose_plan_start_date_2": MessageLookupByLibrary.simpleMessage(
|
||||
"Выберите дату старты плана. Она отобразиться в дневнике пользователя",
|
||||
),
|
||||
@@ -119,6 +130,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"city": MessageLookupByLibrary.simpleMessage("Город"),
|
||||
"city_of_living": MessageLookupByLibrary.simpleMessage("Город проживания"),
|
||||
"collectCharge": MessageLookupByLibrary.simpleMessage("Получить выплату"),
|
||||
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
|
||||
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
|
||||
"commentForMeal": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -138,9 +150,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Ваше обращение принято! Мы рассмотрим его в ближайшее время и примем необходимые меры. Спасибо за вашу бдительность и помощь в улучшении нашего сервиса.",
|
||||
),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"),
|
||||
"congratsYouHaveFinishedPlan": MessageLookupByLibrary.simpleMessage(
|
||||
"Поздравляем, Вы закончили план",
|
||||
),
|
||||
"consultations_count": MessageLookupByLibrary.simpleMessage(
|
||||
"Кол-во проведенных консулдьтаций",
|
||||
),
|
||||
"contactSupport": MessageLookupByLibrary.simpleMessage(
|
||||
"Обратитесь в поддержку",
|
||||
),
|
||||
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
|
||||
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
|
||||
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
|
||||
@@ -361,6 +379,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Нарушение законодательства",
|
||||
),
|
||||
"lbl_english": MessageLookupByLibrary.simpleMessage("English"),
|
||||
"leaveComplainRequest": MessageLookupByLibrary.simpleMessage(
|
||||
"Подать жалобу",
|
||||
),
|
||||
"light_type": MessageLookupByLibrary.simpleMessage("Легкая"),
|
||||
"light_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками.",
|
||||
@@ -385,6 +406,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.",
|
||||
),
|
||||
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
|
||||
"minPlanPrice": MessageLookupByLibrary.simpleMessage(
|
||||
"Минимальная сумма 1200₽",
|
||||
),
|
||||
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
|
||||
"Минимальная цена для покупателя: 1200₽",
|
||||
),
|
||||
@@ -396,6 +420,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"misrepresentation_description": MessageLookupByLibrary.simpleMessage(
|
||||
"Новости, которые могут вызвать широкий резонанс или несут угрозу, распространяемые с целью введения людей в заблуждение.",
|
||||
),
|
||||
"moneySentToCard": MessageLookupByLibrary.simpleMessage(
|
||||
"Средства отправлены на карту",
|
||||
),
|
||||
"more_about_order": MessageLookupByLibrary.simpleMessage(
|
||||
"Подробнее о заказе",
|
||||
),
|
||||
@@ -477,6 +504,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Физическая форма",
|
||||
),
|
||||
"place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"),
|
||||
"planWillBeConfirmedAfterThreeDays": MessageLookupByLibrary.simpleMessage(
|
||||
"Если вы не подтвердите, то план через три дня подтвердится автоматически",
|
||||
),
|
||||
"plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"),
|
||||
"plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"),
|
||||
"plan_desc_example": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -489,6 +519,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"plan_seller": MessageLookupByLibrary.simpleMessage("Продавец плана"),
|
||||
"plan_type": MessageLookupByLibrary.simpleMessage("Тип плана"),
|
||||
"plans_complete": MessageLookupByLibrary.simpleMessage("Пройденные планы"),
|
||||
"pleaseConfirmPlan": MessageLookupByLibrary.simpleMessage(
|
||||
"Просим вас подтвердить прохождение плана",
|
||||
),
|
||||
"please_try_again_later": MessageLookupByLibrary.simpleMessage(
|
||||
"Пожалуйста, попробуйте еще раз позже",
|
||||
),
|
||||
@@ -631,6 +664,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Верхние показатели давления:",
|
||||
),
|
||||
"upper": MessageLookupByLibrary.simpleMessage("Верхнее"),
|
||||
"userConfirmedPlanCompletion": MessageLookupByLibrary.simpleMessage(
|
||||
"Пользователь подтвердил прохождения плана. В ближайшее время Вы получите",
|
||||
),
|
||||
"user_authorization": MessageLookupByLibrary.simpleMessage(
|
||||
"Аутентификация",
|
||||
),
|
||||
@@ -677,6 +713,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"wrong_credentials": MessageLookupByLibrary.simpleMessage(
|
||||
"Неверные данные пользователя",
|
||||
),
|
||||
"youCanComplain": MessageLookupByLibrary.simpleMessage(
|
||||
"Если вас что-то не устроило, вы можете подать жалобу",
|
||||
),
|
||||
"youShureYouWantToCancelOrder": MessageLookupByLibrary.simpleMessage(
|
||||
"Вы уверены, что хотите отменить заказ?",
|
||||
),
|
||||
|
||||
@@ -3074,6 +3074,156 @@ class S {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Поздравляем, Вы закончили план`
|
||||
String get congratsYouHaveFinishedPlan {
|
||||
return Intl.message(
|
||||
'Поздравляем, Вы закончили план',
|
||||
name: 'congratsYouHaveFinishedPlan',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Пользователь подтвердил прохождения плана. В ближайшее время Вы получите`
|
||||
String get userConfirmedPlanCompletion {
|
||||
return Intl.message(
|
||||
'Пользователь подтвердил прохождения плана. В ближайшее время Вы получите',
|
||||
name: 'userConfirmedPlanCompletion',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Получить выплату`
|
||||
String get collectCharge {
|
||||
return Intl.message(
|
||||
'Получить выплату',
|
||||
name: 'collectCharge',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Выплата одобрена`
|
||||
String get chargeApproved {
|
||||
return Intl.message(
|
||||
'Выплата одобрена',
|
||||
name: 'chargeApproved',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Средства отправлены на карту`
|
||||
String get moneySentToCard {
|
||||
return Intl.message(
|
||||
'Средства отправлены на карту',
|
||||
name: 'moneySentToCard',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Не удалось оформить выплату`
|
||||
String get chargeCollectionFailed {
|
||||
return Intl.message(
|
||||
'Не удалось оформить выплату',
|
||||
name: 'chargeCollectionFailed',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Обратитесь в поддержку`
|
||||
String get contactSupport {
|
||||
return Intl.message(
|
||||
'Обратитесь в поддержку',
|
||||
name: 'contactSupport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Минимум 3 символа`
|
||||
String get atLeast3Symbols {
|
||||
return Intl.message(
|
||||
'Минимум 3 символа',
|
||||
name: 'atLeast3Symbols',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Выберите тип плана`
|
||||
String get chosePlanType {
|
||||
return Intl.message(
|
||||
'Выберите тип плана',
|
||||
name: 'chosePlanType',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Минимальная сумма 1200₽`
|
||||
String get minPlanPrice {
|
||||
return Intl.message(
|
||||
'Минимальная сумма 1200₽',
|
||||
name: 'minPlanPrice',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Добавьте хотя бы одину опцию`
|
||||
String get addAtLeastOneItem {
|
||||
return Intl.message(
|
||||
'Добавьте хотя бы одину опцию',
|
||||
name: 'addAtLeastOneItem',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Просим вас подтвердить прохождение плана`
|
||||
String get pleaseConfirmPlan {
|
||||
return Intl.message(
|
||||
'Просим вас подтвердить прохождение плана',
|
||||
name: 'pleaseConfirmPlan',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Если вы не подтвердите, то план через три дня подтвердится автоматически`
|
||||
String get planWillBeConfirmedAfterThreeDays {
|
||||
return Intl.message(
|
||||
'Если вы не подтвердите, то план через три дня подтвердится автоматически',
|
||||
name: 'planWillBeConfirmedAfterThreeDays',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Если вас что-то не устроило, вы можете подать жалобу`
|
||||
String get youCanComplain {
|
||||
return Intl.message(
|
||||
'Если вас что-то не устроило, вы можете подать жалобу',
|
||||
name: 'youCanComplain',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Подать жалобу`
|
||||
String get leaveComplainRequest {
|
||||
return Intl.message(
|
||||
'Подать жалобу',
|
||||
name: 'leaveComplainRequest',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
|
||||
@@ -370,6 +370,21 @@
|
||||
"orderCanceled": "Заказ отменен",
|
||||
"couldNotCancelOrder": "Не удалось отменить заказ",
|
||||
"dayComment": "Комментарий ко всему дню",
|
||||
"mealComment": "Комментарий к приему пищи"
|
||||
"mealComment": "Комментарий к приему пищи",
|
||||
"congratsYouHaveFinishedPlan": "Поздравляем, Вы закончили план",
|
||||
"userConfirmedPlanCompletion": "Пользователь подтвердил прохождения плана. В ближайшее время Вы получите",
|
||||
"collectCharge": "Получить выплату",
|
||||
"chargeApproved": "Выплата одобрена",
|
||||
"moneySentToCard": "Средства отправлены на карту",
|
||||
"chargeCollectionFailed": "Не удалось оформить выплату",
|
||||
"contactSupport": "Обратитесь в поддержку",
|
||||
"atLeast3Symbols": "Минимум 3 символа",
|
||||
"chosePlanType": "Выберите тип плана",
|
||||
"minPlanPrice": "Минимальная сумма 1200₽",
|
||||
"addAtLeastOneItem": "Добавьте хотя бы одину опцию",
|
||||
"pleaseConfirmPlan": "Просим вас подтвердить прохождение плана",
|
||||
"planWillBeConfirmedAfterThreeDays": "Если вы не подтвердите, то план через три дня подтвердится автоматически",
|
||||
"youCanComplain": "Если вас что-то не устроило, вы можете подать жалобу",
|
||||
"leaveComplainRequest": "Подать жалобу"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: eatvio_app
|
||||
description: A new Flutter project.
|
||||
version: 1.0.50+6
|
||||
version: 1.0.51+6
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user