Merge pull request 'feat(InfoByOrder)' (#750) from misha/eatvio_app:issue-681 into main

Reviewed-on: eatvio/eatvio_app#750
This commit is contained in:
2025-10-29 01:39:05 +00:00
10 changed files with 108 additions and 107 deletions

View File

@@ -130,7 +130,7 @@ class CubitRegistrations {
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl())); sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl())); sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl())); sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl())); sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl(), profileRepository: sl()));
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl())); sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl())); sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl())); sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));

View File

@@ -215,7 +215,8 @@ class OtherRoutes {
final data = state.extra as Map<String, dynamic>; final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int; final planId = data['planId'] as int;
final successfulPayment = data['successfulPayment'] as bool?; final successfulPayment = data['successfulPayment'] as bool?;
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment); final customerSlug = data['customerSlug'] as String?;
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment, customerSlug: customerSlug);
}, },
), ),
GoRoute( GoRoute(

View File

@@ -149,10 +149,7 @@ class _ChatScreenState extends State<ChatScreen> {
centerTitle: false, centerTitle: false,
title: GestureDetector( title: GestureDetector(
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed('profileScreen', extra: state.chatRoom.users.receiver.slug);
'profileScreen',
extra: state.chatRoom.users.receiver.slug,
);
}, },
child: Row( child: Row(
children: [ children: [
@@ -447,7 +444,7 @@ class _ChatScreenState extends State<ChatScreen> {
final blockedUsersCubit = sl<BlockedUsersCubit>(); final blockedUsersCubit = sl<BlockedUsersCubit>();
await blockedUsersCubit.getBlockedUsers(); await blockedUsersCubit.getBlockedUsers();
final blockedState = blockedUsersCubit.state; final blockedState = blockedUsersCubit.state;
if (blockedState is BlockedUsersStateLoaded) { if (blockedState is BlockedUsersStateLoaded) {
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == state.chatRoom.users.receiver.slug); final isBlocked = blockedState.profilesList.any((profile) => profile.slug == state.chatRoom.users.receiver.slug);
if (isBlocked) { if (isBlocked) {
@@ -462,7 +459,7 @@ class _ChatScreenState extends State<ChatScreen> {
return; return;
} }
} }
if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) { if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) {
chatRoomCubit.sendMessage( chatRoomCubit.sendMessage(
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(), data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),
@@ -497,6 +494,12 @@ class _ChatScreenState extends State<ChatScreen> {
left: 16, left: 16,
child: AboutOrderButton( child: AboutOrderButton(
onTap: () async { onTap: () async {
if (amIConsultant) {
GoRouter.of(
context,
).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id, 'customerSlug': state.chatRoom.users.receiver.slug});
return;
}
GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id}); GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id});
}, },
), ),

View File

@@ -4,22 +4,24 @@ import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
class AuthorCard extends StatelessWidget { class AuthorCard extends StatelessWidget {
const AuthorCard({super.key, required this.author, required this.title, this.backgroundColor, this.titleStyle}); const AuthorCard({super.key, required this.author, required this.title, this.backgroundColor, this.titleStyle, this.customerProfile});
final Author author; final Author author;
final String title; final String title;
final Color? backgroundColor; final Color? backgroundColor;
final TextStyle? titleStyle; final TextStyle? titleStyle;
final ProfileModel? customerProfile;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
GoRouter.of(context).pushNamed('profileScreen', extra: author.authorSlug); GoRouter.of(context).pushNamed('profileScreen', extra: customerProfile != null ? customerProfile!.slug : author.authorSlug);
}, },
child: Container( child: Container(
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,
@@ -39,24 +41,48 @@ class AuthorCard extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
image: DecorationImage( image: DecorationImage(
image: NetworkImage( image: NetworkImage(
author.authorProfileIcon.contains('https') ? author.authorProfileIcon : sl<AppConfig>().baseUrl + author.authorProfileIcon, customerProfile != null
? customerProfile!.profilePhotoUrl.contains('https')
? customerProfile!.profilePhotoUrl
: sl<AppConfig>().baseUrl + customerProfile!.profilePhotoUrl
: author.authorProfileIcon.contains('https')
? author.authorProfileIcon
: sl<AppConfig>().baseUrl + author.authorProfileIcon,
), ),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
), ),
subtitle: Text(author.authorName, style: TextStyles.textLSemibold.copyWith(color: appTheme.black)), subtitle: Text(
customerProfile != null ? customerProfile!.name : author.authorName,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
title: Text(title, style: titleStyle ?? TextStyles.textSSemibold.copyWith(color: appTheme.gray)), title: Text(title, style: titleStyle ?? TextStyles.textSSemibold.copyWith(color: appTheme.gray)),
), ),
Row( Row(
children: [ children: [
Text('Консультант', style: TextStyles.textMMedium.copyWith(color: appTheme.black)), if (customerProfile == null) ...[
const SizedBox(width: 10), Text('Консультант', style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
if (author.authorPlaceInRating != null) const SizedBox(width: 10),
InfoBouble(
radius: 12, if (author.authorPlaceInRating != null)
backgroundColor: appTheme.whiteA700, InfoBouble(
children: [Text('${author.authorPlaceInRating}-й в рейтинге', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400))], radius: 12,
backgroundColor: appTheme.whiteA700,
children: [
Text('${author.authorPlaceInRating}-й в рейтинге', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400)),
],
),
] else
GestureDetector(
onTap: () {
GoRouter.of(context).pushNamed(UserDetailsScreen.name, extra: customerProfile!.slug);
},
child: InfoBouble(
radius: 12,
backgroundColor: appTheme.whiteA700,
children: [Text('Анкета пользователя', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.blueA400))],
),
), ),
], ],
), ),

View File

@@ -1,12 +1,13 @@
import 'dart:developer'; import 'dart:developer';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart'; import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class OrderInfoCubit extends Cubit<OrderInfoState> { class OrderInfoCubit extends Cubit<OrderInfoState> {
OrderInfoCubit({required this.chatRepository}) : super(OrderInfoState()); OrderInfoCubit({required this.chatRepository, required this.profileRepository}) : super(OrderInfoState());
final ChatRepository chatRepository; final ChatRepository chatRepository;
final ProfileRepository profileRepository;
Future<String?> getPaymentLink() async { Future<String?> getPaymentLink() async {
try { try {
@@ -20,10 +21,14 @@ class OrderInfoCubit extends Cubit<OrderInfoState> {
} }
} }
Future<void> getChosenPlan(int id) async { Future<void> getChosenPlan({required int id, String? customerSlug}) async {
try { try {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
final chosenPlan = await chatRepository.getChosenPlan(id: id); final chosenPlan = await chatRepository.getChosenPlan(id: id);
if (customerSlug != null) {
final profile = await profileRepository.getProfileBySlug(customerSlug);
emit(state.copyWith(customerProfile: profile));
}
emit(state.copyWith(isLoading: false, chosenPlan: chosenPlan)); emit(state.copyWith(isLoading: false, chosenPlan: chosenPlan));
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'order_info_state.freezed.dart'; part 'order_info_state.freezed.dart';
@@ -9,6 +10,7 @@ abstract class OrderInfoState with _$OrderInfoState {
factory OrderInfoState({ factory OrderInfoState({
@Default(true) bool isLoading, @Default(true) bool isLoading,
@Default(null) ChosenPlanModel? chosenPlan, @Default(null) ChosenPlanModel? chosenPlan,
@Default(null) ProfileModel? customerProfile,
}) = _OrderInfoState; }) = _OrderInfoState;
} }

View File

@@ -24,31 +24,24 @@ import 'package:go_router/go_router.dart';
class InfoByOrderScreen extends StatelessWidget { class InfoByOrderScreen extends StatelessWidget {
static const name = 'infoByOrderScreen'; static const name = 'infoByOrderScreen';
static const path = '/info-by-order-screen'; static const path = '/info-by-order-screen';
const InfoByOrderScreen({super.key, this.successfulPayment, required this.planId}); const InfoByOrderScreen({super.key, this.successfulPayment, required this.planId, this.customerSlug});
final bool? successfulPayment; final bool? successfulPayment;
final int planId; final int planId;
final String? customerSlug;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => sl<OrderInfoCubit>()..getChosenPlan(planId), create: (context) => sl<OrderInfoCubit>()..getChosenPlan(id: planId, customerSlug: customerSlug),
child: InfoByOrderScreenContent( child: InfoByOrderScreenContent(successfulPayment: successfulPayment, planId: planId, customerSlug: customerSlug),
successfulPayment: successfulPayment,
planId: planId,
),
); );
} }
} }
class InfoByOrderScreenContent extends StatefulWidget { class InfoByOrderScreenContent extends StatefulWidget {
static const name = 'infoByOrderScreen'; const InfoByOrderScreenContent({super.key, required this.planId, required this.successfulPayment, this.customerSlug});
static const path = '/info-by-order-screen';
const InfoByOrderScreenContent({
super.key,
required this.planId,
required this.successfulPayment,
});
final int planId; final int planId;
final bool? successfulPayment; final bool? successfulPayment;
final String? customerSlug;
@override @override
State<InfoByOrderScreenContent> createState() => _InfoByOrderScreenContentState(); State<InfoByOrderScreenContent> createState() => _InfoByOrderScreenContentState();
} }
@@ -64,39 +57,30 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
if (state.isLoading) { if (state.isLoading) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(s.infoAboutOrder, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); GoRouter.of(context).pop();
}), },
), ),
body: const Center(
child: Loader(),
), ),
body: const Center(child: Loader()),
); );
} }
if (plan == null) { if (plan == null) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(s.infoAboutOrder, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); GoRouter.of(context).pop();
}), },
),
), ),
body: Center( body: Center(
child: Text( child: Text(s.sorry_we_did_not_find_the_plan, style: TextStyles.textSSemibold.copyWith(color: appTheme.black)),
s.sorry_we_did_not_find_the_plan,
style: TextStyles.textSSemibold.copyWith(color: appTheme.black),
),
), ),
); );
} }
@@ -122,58 +106,35 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
return Scaffold( return Scaffold(
backgroundColor: appTheme.gray100, backgroundColor: appTheme.gray100,
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(s.infoAboutOrder, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); GoRouter.of(context).pop();
}), },
),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
if (plan.chosenPlanState == ChosenPlanStateEnum.stateDone) ...[ if (plan.chosenPlanState == ChosenPlanStateEnum.stateDone) ...[CollectChargeBanner(chosenPlan: plan, price: chosenOption?.price ?? 0)],
CollectChargeBanner( if (widget.successfulPayment != null) ...[const SizedBox(height: 8), PaymentBanner(successfulPayment: widget.successfulPayment ?? false)],
chosenPlan: plan,
price: chosenOption?.price ?? 0,
)
],
if (widget.successfulPayment != null) ...[
const SizedBox(height: 8),
PaymentBanner(
successfulPayment: widget.successfulPayment ?? false,
),
],
const SizedBox(height: 8), const SizedBox(height: 8),
AuthorCard( AuthorCard(
author: plan.plan.author, author: plan.plan.author,
title: s.plan_seller, title: state.customerProfile != null ? s.customer : s.plan_seller,
titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray), titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray),
backgroundColor: appTheme.whiteA700, backgroundColor: appTheme.whiteA700,
customerProfile: state.customerProfile,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
PlanProps( PlanProps(planDuration: plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: planStatus),
planDuration: plan.plan.duration,
planPrice: chosenOption?.price.toDouble() ?? 0,
planStatus: planStatus),
const SizedBox(height: 8), const SizedBox(height: 8),
PlanInfo( PlanInfo(title: plan.plan.name, description: plan.plan.details, imagePath: plan.plan.image),
title: plan.plan.name,
description: plan.plan.details,
imagePath: plan.plan.image,
),
const SizedBox(height: 8), const SizedBox(height: 8),
OptionWidget( OptionWidget(option: chosenOption, disableDuration: true),
option: chosenOption,
disableDuration: true,
),
const SizedBox(height: 8), const SizedBox(height: 8),
PlanState( PlanState(state: plan.chosenPlanState),
state: plan.chosenPlanState,
),
const SizedBox(height: 20), const SizedBox(height: 20),
if (widget.successfulPayment == null) if (widget.successfulPayment == null)
CustomOutlinedButton( CustomOutlinedButton(
@@ -184,13 +145,10 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
onPressed: () async { onPressed: () async {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => CancelPlanBottomSheet( builder: (context) => CancelPlanBottomSheet(enableForm: plan.chosenPlanState.isStateAfterPaidState(), planId: plan.id),
enableForm: plan.chosenPlanState.isStateAfterPaidState(),
planId: plan.id,
),
); );
}, },
) ),
], ],
), ),
), ),

View File

@@ -249,6 +249,7 @@ class MessageLookup extends MessageLookupByLibrary {
"cunsutation_was_paid_successfully": MessageLookupByLibrary.simpleMessage( "cunsutation_was_paid_successfully": MessageLookupByLibrary.simpleMessage(
"Поздравляем, консультация успешно оплачена", "Поздравляем, консультация успешно оплачена",
), ),
"customer": MessageLookupByLibrary.simpleMessage("Покупатель"),
"customer_price": MessageLookupByLibrary.simpleMessage( "customer_price": MessageLookupByLibrary.simpleMessage(
"Цена для покупателя", "Цена для покупателя",
), ),

View File

@@ -28,10 +28,9 @@ class S {
static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> load(Locale locale) { static Future<S> load(Locale locale) {
final name = final name = (locale.countryCode?.isEmpty ?? false)
(locale.countryCode?.isEmpty ?? false) ? locale.languageCode
? locale.languageCode : locale.toString();
: locale.toString();
final localeName = Intl.canonicalizedLocale(name); final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) { return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName; Intl.defaultLocale = localeName;
@@ -3499,6 +3498,11 @@ class S {
args: [], args: [],
); );
} }
/// `Покупатель`
String get customer {
return Intl.message('Покупатель', name: 'customer', desc: '', args: []);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<S> { class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -414,6 +414,7 @@
"delete": "Удалить", "delete": "Удалить",
"cannotDeleteAccount": "Не получилось удалить ваш аккаунт", "cannotDeleteAccount": "Не получилось удалить ваш аккаунт",
"resend": "Отправить повторно", "resend": "Отправить повторно",
"notifications": "Уведомления" "notifications": "Уведомления",
"customer": "Покупатель"
} }