fix(diary): added proccesing of future dates and fixed deleted portions

This commit is contained in:
Username1548
2025-04-08 21:28:40 +07:00
parent 245c854754
commit 823dc78490
25 changed files with 1038 additions and 371 deletions

View File

@@ -35,7 +35,6 @@ import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_s
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/pages/notifications_screen.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
@@ -259,15 +258,27 @@ class AppRouter {
GoRoute(
path: InfoByOrderScreen.path,
name: InfoByOrderScreen.name,
builder: (context, state) => InfoByOrderScreen(
plan: state.extra as ChosenPlanModel,
)),
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int;
final successfulPayment = data['successfulPayment'] as bool?;
return InfoByOrderScreen(
planId: planId,
successfulPayment: successfulPayment,
);
}),
GoRoute(
path: PaymentScreen.path,
name: PaymentScreen.name,
builder: (context, state) => PaymentScreen(
url: state.extra as String,
)),
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int;
final url = data['url'] as String;
return PaymentScreen(
url: url,
planId: planId,
);
}),
],
);
}

View File

@@ -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];

View File

@@ -55,6 +55,7 @@ import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
import 'package:eatvio_app/features/plans/services/plans_service.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
@@ -121,6 +122,7 @@ Future<void> init() async {
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));

View File

@@ -350,7 +350,8 @@ class _ChatScreenState extends State<ChatScreen> {
hintText: 'Сообщение',
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
filled: true,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(100),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
@@ -394,7 +395,7 @@ class _ChatScreenState extends State<ChatScreen> {
left: 16,
child: AboutOrderButton(
onTap: () {
GoRouter.of(context).push(InfoByOrderScreen.path, extra: state.chatRoom.chosenPlan);
GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id});
},
)),
],

View File

@@ -1,13 +1,14 @@
import 'dart:developer';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:webview_flutter/webview_flutter.dart';
class PaymentScreen extends StatefulWidget {
static const name = 'paymentScreen';
static const path = '/payment-screen';
final String url;
const PaymentScreen({super.key, required this.url});
final int planId;
const PaymentScreen({super.key, required this.url, required this.planId});
@override
State<PaymentScreen> createState() => _PaymentScreenState();
@@ -23,13 +24,14 @@ class _PaymentScreenState extends State<PaymentScreen> {
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onHttpError: (HttpResponseError error) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
log(request.url);
if (request.url.contains('tbank/checkout-success')) {
if (request.url.contains('Success=true')) {
GoRouter.of(context).pushReplacement(InfoByOrderScreen.path, extra: {'planId': widget.planId, 'successfulPayment': true});
} else {
GoRouter.of(context).pushReplacement(InfoByOrderScreen.path, extra: {'planId': widget.planId, 'successfulPayment': false});
}
}
return NavigationDecision.navigate;
},
),

View File

@@ -1,6 +1,8 @@
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/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/generated/l10n.dart';
@@ -9,9 +11,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
class NotificationBanner extends StatelessWidget {
class NotificationBanner extends StatefulWidget {
const NotificationBanner({super.key, required this.bannerType});
final BannerType bannerType;
@override
State<NotificationBanner> createState() => _NotificationBannerState();
}
class _NotificationBannerState extends State<NotificationBanner> {
bool isLoading = false;
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
@@ -28,65 +37,81 @@ class NotificationBanner extends StatelessWidget {
padding: const EdgeInsets.only(top: 12, right: 8, left: 12),
child: SvgPicture.asset('assets/icons/reversed_exclamation_mark.svg', height: 68, width: 68),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: switch (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: () {},
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 paymentLink = await context.read<ChatRoomCubit>().getPaymentLink();
if (paymentLink != null) {
if (!context.mounted) return;
GoRouter.of(context).push(PaymentScreen.path, extra: paymentLink);
}
},
child: Text(s.pay, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
),
const SizedBox(height: 16),
],
},
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: () {},
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),
],
},
),
),
),
],
),
);

View File

@@ -5,14 +5,18 @@ part 'diary_day_model.freezed.dart';
part 'diary_day_model.g.dart';
enum PortionState {
active,
@JsonValue('filled_diet')
filledDiet,
@JsonValue('eaten_from_diet')
eatenFromDiet,
eaten,
@JsonValue('eaten_deleted')
eatenDeleted,
@JsonValue('eaten_edited')
eatenEdited,
commented,
revieweByUser,
@JsonValue('reviewed_by_user')
reviewedByUser,
}
@freezed

View File

@@ -113,14 +113,13 @@ Map<String, dynamic> _$$PortionModelImplToJson(_$PortionModelImpl instance) =>
};
const _$PortionStateEnumMap = {
PortionState.active: 'active',
PortionState.filledDiet: 'filledDiet',
PortionState.eatenFromDiet: 'eatenFromDiet',
PortionState.filledDiet: 'filled_diet',
PortionState.eatenFromDiet: 'eaten_from_diet',
PortionState.eaten: 'eaten',
PortionState.eatenDeleted: 'eatenDeleted',
PortionState.eatenEdited: 'eatenEdited',
PortionState.eatenDeleted: 'eaten_deleted',
PortionState.eatenEdited: 'eaten_edited',
PortionState.commented: 'commented',
PortionState.revieweByUser: 'revieweByUser',
PortionState.reviewedByUser: 'reviewed_by_user',
};
_$TypeOfDiaryModelImpl _$$TypeOfDiaryModelImplFromJson(

View File

@@ -39,17 +39,19 @@ class DiaryCubit extends Cubit<DiaryState> {
}
try {
final diaryDay = await diaryRepository.getDiaryByDate('$selectedDay');
final nutrition = await getNutrition(diaryDay.id);
emit(
DiaryState.loaded(
diary: diaryDay,
nutrition: await getNutrition((await diaryRepository.getDiaryByDate('$selectedDay')).id),
nutrition: nutrition,
selectedDay: selectedDay,
selectedMeal: selectedMeal,
),
);
} catch (e) {
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(DiaryState.error(errorMessage: e.toString()));
rethrow;
}
}
@@ -181,10 +183,12 @@ class DiaryCubit extends Cubit<DiaryState> {
fillSatiety({required int diaryId, required int satiety}) async {
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
await fetchDiary();
}
fillMood({required int diaryId, required int mood}) async {
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
await fetchDiary();
}
fillComment(String comment) {

View File

@@ -19,5 +19,6 @@ class DiaryState with _$DiaryState {
required NutritionDataModel nutrition,
required DateTime selectedDay,
required int selectedMeal,
}) = DiaryStateLoaded;
}

View File

@@ -33,7 +33,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
}
if (state is DiaryStateError) {
return Scaffold(
body: DataErrorWidget(onReload: context.read<DiaryCubit>().fetchDiary()),
body: DataErrorWidget(onReload: () => context.read<DiaryCubit>().fetchDiary()),
);
}
return Scaffold(
@@ -48,12 +48,15 @@ class _DiaryScreenState extends State<DiaryScreen> {
children: [
const CalendarWidget(),
if (state is DiaryStateLoading) const Loader(),
if (state is DiaryStateLoaded) const WakeupAndRetireWidget(),
if (state is DiaryStateLoaded) WakeupAndRetireWidget(showRetire: state.diary.typeOfDiary.hasChosenPlan),
const SizedBox(height: 12),
if (state is DiaryStateLoaded) const MealWidget(),
if (state is DiaryStateLoaded)
MealWidget(
showMood: state.diary.typeOfDiary.hasChosenPlan,
),
if (state is DiaryStateLoaded) const CaloriesChartWidget(),
const SizedBox(height: 12),
if (state is DiaryStateLoaded) const CommentForConsultantWidget(),
if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) const CommentForConsultantWidget(),
if (state is DiaryStateLoaded && state.diary.closed == 0)
Padding(
padding: const EdgeInsets.only(top: 12),

View File

@@ -1,6 +1,8 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
@@ -19,6 +21,7 @@ class _CalendarWidgetState extends State<CalendarWidget> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocConsumer<DiaryCubit, DiaryState>(
listener: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
@@ -48,6 +51,11 @@ class _CalendarWidgetState extends State<CalendarWidget> {
),
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
final today = DateTime.now();
if (selectedDay.isAfter(today)) {
showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day);
return;
}
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;

View File

@@ -8,8 +8,8 @@ import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_pick
import 'package:flutter/material.dart';
class MealWidget extends StatefulWidget {
const MealWidget({super.key});
const MealWidget({super.key, required this.showMood});
final bool showMood;
@override
State<MealWidget> createState() => _MealWidgetState();
}
@@ -20,12 +20,12 @@ class _MealWidgetState extends State<MealWidget> {
return Material(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
child: const Padding(
padding: EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
const Text(
'Прием пищи',
style: TextStyle(
color: Color(0xFF323F4B),
@@ -33,8 +33,8 @@ class _MealWidgetState extends State<MealWidget> {
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 16),
Row(
const SizedBox(height: 16),
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MealButtonWidget(mealNumber: 1),
@@ -44,18 +44,20 @@ class _MealWidgetState extends State<MealWidget> {
MealButtonWidget(mealNumber: 5),
],
),
SizedBox(height: 12),
TimeOfMealPickerWidget(),
SizedBox(height: 12),
ProductsListWidget(),
SizedBox(height: 12),
AddProductButtonWidget(),
SizedBox(height: 12),
PhotoGalleryWidget(),
SizedBox(height: 12),
SatietyRatingWidget(),
SizedBox(height: 12),
MoodRatingWidget(),
const SizedBox(height: 12),
const TimeOfMealPickerWidget(),
const SizedBox(height: 12),
const ProductsListWidget(),
const SizedBox(height: 12),
const AddProductButtonWidget(),
const SizedBox(height: 12),
const PhotoGalleryWidget(),
const SizedBox(height: 12),
const SatietyRatingWidget(),
if (widget.showMood) ...[
const SizedBox(height: 12),
const MoodRatingWidget(),
]
],
),
),

View File

@@ -14,17 +14,18 @@ class MoodRatingWidget extends StatefulWidget {
}
class _MoodRatingWidgetState extends State<MoodRatingWidget> {
late double currentSelectedRating;
bool isFirstFetch = true;
List<double> currentSelectedRating = [0, 0, 0, 0, 0];
List<bool> isFirstFetch = [true, true, true, true, true];
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
if (isFirstFetch) {
currentSelectedRating = state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1;
isFirstFetch = false;
final selectedIndex = state.selectedMeal - 1;
if (isFirstFetch[selectedIndex]) {
currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].mood.toDouble() + 1;
isFirstFetch[selectedIndex] = false;
}
return Padding(
padding: const EdgeInsets.only(top: 24),
@@ -46,7 +47,7 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
width: 32,
),
RatingBar.builder(
initialRating: state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1,
initialRating: state.diary.meals[selectedIndex].mood.toDouble() + 1,
itemCount: 6,
itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
updateOnDrag: true,
@@ -56,129 +57,129 @@ class _MoodRatingWidgetState extends State<MoodRatingWidget> {
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFC82222),
border: currentSelectedRating == 1
border: currentSelectedRating[selectedIndex] == 1
? Border.all(
width: 2,
color: const Color(0xFFAA3333),
strokeAlign: BorderSide.strokeAlignOutside,
)
width: 2,
color: const Color(0xFFAA3333),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 1
borderRadius: currentSelectedRating[selectedIndex] == 1
? const BorderRadius.horizontal(
left: Radius.circular(8),
right: Radius.circular(2),
)
left: Radius.circular(8),
right: Radius.circular(2),
)
: const BorderRadius.horizontal(
left: Radius.circular(8),
),
left: Radius.circular(8),
),
),
);
case 1:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFFF3E3E),
border: currentSelectedRating == 2
border: currentSelectedRating[selectedIndex] == 2
? Border.all(
width: 2,
color: const Color(0xFF550000),
strokeAlign: BorderSide.strokeAlignOutside,
)
width: 2,
color: const Color(0xFF550000),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null,
),
);
case 2:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFF8C926),
border: currentSelectedRating == 3
border: currentSelectedRating[selectedIndex] == 3
? Border.all(
width: 2,
color: const Color(0xFF8C5D00),
strokeAlign: BorderSide.strokeAlignOutside,
)
width: 2,
color: const Color(0xFF8C5D00),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null,
),
);
case 3:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFFFF3BF),
border: currentSelectedRating == 4
border: currentSelectedRating[selectedIndex] == 4
? Border.all(
width: 2,
color: const Color(0xFFB5911B),
strokeAlign: BorderSide.strokeAlignOutside,
)
width: 2,
color: const Color(0xFFB5911B),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null,
),
);
case 4:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFF62E833),
border: currentSelectedRating == 5
border: currentSelectedRating[selectedIndex] == 5
? Border.all(
width: 2,
color: const Color(0xFF276918),
strokeAlign: BorderSide.strokeAlignOutside,
)
width: 2,
color: const Color(0xFF276918),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null,
),
);
default:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFF4DCE20),
border: currentSelectedRating == 6
border: currentSelectedRating[selectedIndex] == 6
? Border.all(
width: 2,
color: const Color(0xFF184D12),
strokeAlign: BorderSide.strokeAlignOutside,
)
width: 2,
color: const Color(0xFF184D12),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 6
borderRadius: currentSelectedRating[selectedIndex] == 6
? const BorderRadius.horizontal(
left: Radius.circular(2),
right: Radius.circular(8),
)
left: Radius.circular(2),
right: Radius.circular(8),
)
: const BorderRadius.horizontal(
right: Radius.circular(8),
),
right: Radius.circular(8),
),
),
);
}
},
onRatingUpdate: (value) {
setState(() {
currentSelectedRating = value;
currentSelectedRating[selectedIndex] = value;
});
EasyDebounce.debounce(
'change-satiety',
const Duration(milliseconds: 250),
() {
context.read<DiaryCubit>().fillMood(diaryId: state.diary.id, mood: currentSelectedRating.toInt() - 1);
() {
context.read<DiaryCubit>().fillMood(diaryId: state.diary.id, mood: currentSelectedRating[selectedIndex].toInt() - 1);
},
);
},

View File

@@ -14,17 +14,18 @@ class SatietyRatingWidget extends StatefulWidget {
}
class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
late double currentSelectedRating;
bool isFirstFetch = true;
List<double> currentSelectedRating = [0, 0, 0, 0, 0];
List<bool> isFirstFetch = [true, true, true, true, true];
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
if (isFirstFetch) {
currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1;
isFirstFetch = false;
final selectedIndex = state.selectedMeal - 1;
if (isFirstFetch[selectedIndex]) {
currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].satiety.toDouble() + 1;
isFirstFetch[selectedIndex] = false;
}
return Padding(
padding: const EdgeInsets.only(top: 24),
@@ -46,7 +47,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
width: 32,
),
RatingBar.builder(
initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1,
initialRating: state.diary.meals[selectedIndex].satiety.toDouble() + 1,
itemCount: 6,
itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
updateOnDrag: true,
@@ -56,17 +57,17 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 1 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFC82222),
border: currentSelectedRating == 1
border: currentSelectedRating[selectedIndex] == 1
? Border.all(
width: 2,
color: const Color(0xFFAA3333),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 1
borderRadius: currentSelectedRating[selectedIndex] == 1
? const BorderRadius.horizontal(
left: Radius.circular(8),
right: Radius.circular(2),
@@ -80,85 +81,85 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 2 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFFF3E3E),
border: currentSelectedRating == 2
border: currentSelectedRating[selectedIndex] == 2
? Border.all(
width: 2,
color: const Color(0xFF550000),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null,
),
);
case 2:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 3 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFF8C926),
border: currentSelectedRating == 3
border: currentSelectedRating[selectedIndex] == 3
? Border.all(
width: 2,
color: const Color(0xFF8C5D00),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null,
),
);
case 3:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 4 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFFFFF3BF),
border: currentSelectedRating == 4
border: currentSelectedRating[selectedIndex] == 4
? Border.all(
width: 2,
color: const Color(0xFFB5911B),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null,
),
);
case 4:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 5 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFF62E833),
border: currentSelectedRating == 5
border: currentSelectedRating[selectedIndex] == 5
? Border.all(
width: 2,
color: const Color(0xFF276918),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null,
borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null,
),
);
default:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null,
margin: currentSelectedRating[selectedIndex] == 6 ? const EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: const Color(0xFF4DCE20),
border: currentSelectedRating == 6
border: currentSelectedRating[selectedIndex] == 6
? Border.all(
width: 2,
color: const Color(0xFF184D12),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 6
borderRadius: currentSelectedRating[selectedIndex] == 6
? const BorderRadius.horizontal(
left: Radius.circular(2),
right: Radius.circular(8),
@@ -172,13 +173,13 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
},
onRatingUpdate: (value) {
setState(() {
currentSelectedRating = value;
currentSelectedRating[selectedIndex] = value;
});
EasyDebounce.debounce(
'change-satiety',
const Duration(milliseconds: 250),
() {
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt() - 1);
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating[selectedIndex].toInt() - 1);
},
);
},

View File

@@ -8,8 +8,8 @@ import 'package:flutter_svg/flutter_svg.dart';
import '../../../../core/theme/theme_helper.dart';
class WakeupAndRetireWidget extends StatefulWidget {
const WakeupAndRetireWidget({super.key});
const WakeupAndRetireWidget({super.key, required this.showRetire});
final bool showRetire;
@override
State<WakeupAndRetireWidget> createState() => _WakeupAndRetireWidgetState();
}
@@ -29,65 +29,62 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
showModalBottomSheet<void>(
context: context,
useRootNavigator: true,
builder:
(context) => SizedBox(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
use24hFormat: true,
initialDateTime: DateTime.now(),
onDateTimeChanged: (newTime) {
if (isWakeup) {
wakeup = newTime;
} else {
retire = newTime;
}
},
),
),
InkWell(
onTap: () {
Navigator.of(context).pop();
if (isWakeup) {
diaryCubit.fillWakeup(
diaryId: diaryId,
time:
'${wakeup.hour.toString().padLeft(2, '0')}:${wakeup.minute.toString().padLeft(2, '0')}:00',
);
} else {
diaryCubit.fillRetire(
diaryId: diaryId,
time:
'${retire.hour.toString().padLeft(2, '0')}:${retire.minute.toString().padLeft(2, '0')}:00',
);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 16,
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: const Center(
child: Text(
'Сохранить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
builder: (context) => SizedBox(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
use24hFormat: true,
initialDateTime: DateTime.now(),
onDateTimeChanged: (newTime) {
if (isWakeup) {
wakeup = newTime;
} else {
retire = newTime;
}
},
),
),
),
InkWell(
onTap: () {
Navigator.of(context).pop();
if (isWakeup) {
diaryCubit.fillWakeup(
diaryId: diaryId,
time: '${wakeup.hour.toString().padLeft(2, '0')}:${wakeup.minute.toString().padLeft(2, '0')}:00',
);
} else {
diaryCubit.fillRetire(
diaryId: diaryId,
time: '${retire.hour.toString().padLeft(2, '0')}:${retire.minute.toString().padLeft(2, '0')}:00',
);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 16,
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: const Center(
child: Text(
'Сохранить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
),
);
}
@@ -141,9 +138,9 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
state.diary.wakeup.toString().substring(
0,
state.diary.wakeup.toString().length - 3,
),
0,
state.diary.wakeup.toString().length - 3,
),
style: const TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
@@ -158,65 +155,67 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
),
),
),
const SizedBox(width: 8),
Expanded(
child: Material(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset('assets/icons/retire.svg'),
const SizedBox(width: 8),
const Text(
'Уход ко сну',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
],
),
const SizedBox(height: 8),
InkWell(
onTap: () => showTimePicker(state.diary.id, false),
borderRadius: BorderRadius.circular(100),
child: Ink(
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: const Color(0xFFF1F2F6),
),
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
state.diary.retire.toString().substring(
0,
state.diary.retire.toString().length - 3,
),
style: const TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
if (widget.showRetire) ...[
const SizedBox(width: 8),
Expanded(
child: Material(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset('assets/icons/retire.svg'),
const SizedBox(width: 8),
const Text(
'Уход ко сну',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
],
),
const SizedBox(height: 8),
InkWell(
onTap: () => showTimePicker(state.diary.id, false),
borderRadius: BorderRadius.circular(100),
child: Ink(
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: const Color(0xFFF1F2F6),
),
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
state.diary.retire.toString().substring(
0,
state.diary.retire.toString().length - 3,
),
style: const TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
],
],
),
),
),
),
),
]
],
),
);

View File

@@ -0,0 +1,32 @@
import 'dart:developer';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class OrderInfoCubit extends Cubit<OrderInfoState> {
OrderInfoCubit({required this.chatRepository}) : super(OrderInfoState());
final ChatRepository chatRepository;
Future<String?> getPaymentLink() async {
try {
final plan = state.chosenPlan;
if (plan == null) return null;
final paymentModel = await chatRepository.getPaymentLink(id: plan.id);
return paymentModel.paymentUrl;
} catch (e) {
log(e.toString());
return null;
}
}
Future<void> getChosenPlan(int id) async{
try {
emit(state.copyWith(isLoading: true));
emit(state.copyWith(isLoading: false));
} catch (e) {
log(e.toString());
emit(state.copyWith(isLoading: false));
}
}
}

View File

@@ -0,0 +1,14 @@
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'order_info_state.freezed.dart';
@freezed
class OrderInfoState with _$OrderInfoState {
factory OrderInfoState({
@Default(false) bool isLoading,
@Default(null) ChosenPlanModel? chosenPlan,
}) = _OrderInfoState;
}

View File

@@ -0,0 +1,185 @@
// 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 'order_info_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$OrderInfoState {
bool get isLoading => throw _privateConstructorUsedError;
ChosenPlanModel? get chosenPlan => throw _privateConstructorUsedError;
/// Create a copy of OrderInfoState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OrderInfoStateCopyWith<OrderInfoState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $OrderInfoStateCopyWith<$Res> {
factory $OrderInfoStateCopyWith(
OrderInfoState value, $Res Function(OrderInfoState) then) =
_$OrderInfoStateCopyWithImpl<$Res, OrderInfoState>;
@useResult
$Res call({bool isLoading, ChosenPlanModel? chosenPlan});
$ChosenPlanModelCopyWith<$Res>? get chosenPlan;
}
/// @nodoc
class _$OrderInfoStateCopyWithImpl<$Res, $Val extends OrderInfoState>
implements $OrderInfoStateCopyWith<$Res> {
_$OrderInfoStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OrderInfoState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isLoading = null,
Object? chosenPlan = freezed,
}) {
return _then(_value.copyWith(
isLoading: null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
chosenPlan: freezed == chosenPlan
? _value.chosenPlan
: chosenPlan // ignore: cast_nullable_to_non_nullable
as ChosenPlanModel?,
) as $Val);
}
/// Create a copy of OrderInfoState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ChosenPlanModelCopyWith<$Res>? get chosenPlan {
if (_value.chosenPlan == null) {
return null;
}
return $ChosenPlanModelCopyWith<$Res>(_value.chosenPlan!, (value) {
return _then(_value.copyWith(chosenPlan: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$OrderInfoStateImplCopyWith<$Res>
implements $OrderInfoStateCopyWith<$Res> {
factory _$$OrderInfoStateImplCopyWith(_$OrderInfoStateImpl value,
$Res Function(_$OrderInfoStateImpl) then) =
__$$OrderInfoStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isLoading, ChosenPlanModel? chosenPlan});
@override
$ChosenPlanModelCopyWith<$Res>? get chosenPlan;
}
/// @nodoc
class __$$OrderInfoStateImplCopyWithImpl<$Res>
extends _$OrderInfoStateCopyWithImpl<$Res, _$OrderInfoStateImpl>
implements _$$OrderInfoStateImplCopyWith<$Res> {
__$$OrderInfoStateImplCopyWithImpl(
_$OrderInfoStateImpl _value, $Res Function(_$OrderInfoStateImpl) _then)
: super(_value, _then);
/// Create a copy of OrderInfoState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isLoading = null,
Object? chosenPlan = freezed,
}) {
return _then(_$OrderInfoStateImpl(
isLoading: null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
chosenPlan: freezed == chosenPlan
? _value.chosenPlan
: chosenPlan // ignore: cast_nullable_to_non_nullable
as ChosenPlanModel?,
));
}
}
/// @nodoc
class _$OrderInfoStateImpl implements _OrderInfoState {
_$OrderInfoStateImpl({this.isLoading = false, this.chosenPlan = null});
@override
@JsonKey()
final bool isLoading;
@override
@JsonKey()
final ChosenPlanModel? chosenPlan;
@override
String toString() {
return 'OrderInfoState(isLoading: $isLoading, chosenPlan: $chosenPlan)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$OrderInfoStateImpl &&
(identical(other.isLoading, isLoading) ||
other.isLoading == isLoading) &&
(identical(other.chosenPlan, chosenPlan) ||
other.chosenPlan == chosenPlan));
}
@override
int get hashCode => Object.hash(runtimeType, isLoading, chosenPlan);
/// Create a copy of OrderInfoState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OrderInfoStateImplCopyWith<_$OrderInfoStateImpl> get copyWith =>
__$$OrderInfoStateImplCopyWithImpl<_$OrderInfoStateImpl>(
this, _$identity);
}
abstract class _OrderInfoState implements OrderInfoState {
factory _OrderInfoState(
{final bool isLoading,
final ChosenPlanModel? chosenPlan}) = _$OrderInfoStateImpl;
@override
bool get isLoading;
@override
ChosenPlanModel? get chosenPlan;
/// Create a copy of OrderInfoState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OrderInfoStateImplCopyWith<_$OrderInfoStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -1,80 +1,172 @@
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/loader.dart';
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/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';
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_state.dart';
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_status_box.dart';
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:eatvio_app/core/widgets/option_widget.dart';
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
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 InfoByOrderScreen extends StatefulWidget {
class InfoByOrderScreen extends StatelessWidget {
static const name = 'infoByOrderScreen';
static const path = '/info-by-order-screen';
final ChosenPlanModel plan;
const InfoByOrderScreen({
super.key,
required this.plan,
});
const InfoByOrderScreen({super.key, this.successfulPayment, required this.planId});
final bool? successfulPayment;
final int planId;
@override
State<InfoByOrderScreen> createState() => _InfoByOrderScreenState();
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => sl<OrderInfoCubit>()..getChosenPlan(planId),
child: InfoByOrderScreenContent(
successfulPayment: successfulPayment,
planId: planId,
),
);
}
}
class _InfoByOrderScreenState extends State<InfoByOrderScreen> {
class InfoByOrderScreenContent extends StatefulWidget {
static const name = 'infoByOrderScreen';
static const path = '/info-by-order-screen';
const InfoByOrderScreenContent({
super.key,
required this.planId,
required this.successfulPayment,
});
final int planId;
final bool? successfulPayment;
@override
State<InfoByOrderScreenContent> createState() => _InfoByOrderScreenContentState();
}
class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
PacketOptions? chosenOption;
if (widget.plan.optionType == 'options1') {
chosenOption = widget.plan.plan.packet1Options;
} else if (widget.plan.optionType == 'options2') {
chosenOption = widget.plan.plan.packet2Options;
} else {
chosenOption = widget.plan.plan.packet3Options;
}
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
title: Text(
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () {}),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 8),
AuthorCard(
author: widget.plan.plan.author,
title: s.plan_seller,
titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray),
backgroundColor: appTheme.whiteA700,
return BlocBuilder<OrderInfoCubit, OrderInfoState>(
builder: (context, state) {
final plan = state.chosenPlan;
if (state.isLoading) {
Scaffold(
appBar: AppBar(
title: Text(
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 8),
PlanProps(planDuration: widget.plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: PlanStatusValues.paymentFailed),
const SizedBox(height: 8),
PlanInfo(
title: widget.plan.plan.name,
description: widget.plan.plan.details,
imagePath: widget.plan.plan.image,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
GoRouter.of(context).pop();
}),
),
body: const Center(
child: Loader(),
),
);
}
if (plan == null) {
return Scaffold(
appBar: AppBar(
title: Text(
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 8),
OptionWidget(
option: chosenOption,
disableDuration: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
GoRouter.of(context).pop();
}),
),
body: Center(
child: Text(
s.sorry_we_did_not_find_the_plan,
style: TextStyles.textSSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 8),
PlanState(
state: widget.plan.chosenPlanState,
),
);
}
PacketOptions? chosenOption;
if (plan.optionType == 'options1') {
chosenOption = plan.plan.packet1Options;
} else if (plan.optionType == 'options2') {
chosenOption = plan.plan.packet2Options;
} else {
chosenOption = plan.plan.packet3Options;
}
PlanStatusValues planStatus;
if (widget.successfulPayment == true) {
planStatus = PlanStatusValues.paid;
} else if (widget.successfulPayment == false) {
planStatus = PlanStatusValues.paymentFailed;
} else if (plan.chosenPlanState == ChosenPlanStateEnum.statePaid) {
planStatus = PlanStatusValues.paid;
} else {
planStatus = PlanStatusValues.pendingPayment;
}
return Scaffold(
backgroundColor: appTheme.gray100,
appBar: AppBar(
title: Text(
s.infoAboutOrder,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
const SizedBox(height: 30),
],
),
));
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
GoRouter.of(context).pop();
}),
),
body: SingleChildScrollView(
child: Column(
children: [
if (widget.successfulPayment != null) ...[
const SizedBox(height: 8),
PaymentBanner(
successfulPayment: widget.successfulPayment ?? false,
),
],
const SizedBox(height: 8),
AuthorCard(
author: plan.plan.author,
title: s.plan_seller,
titleStyle: TextStyles.textSSemibold.copyWith(color: appTheme.gray),
backgroundColor: appTheme.whiteA700,
),
const SizedBox(height: 8),
PlanProps(planDuration: plan.plan.duration, planPrice: chosenOption?.price.toDouble() ?? 0, planStatus: planStatus),
const SizedBox(height: 8),
PlanInfo(
title: plan.plan.name,
description: plan.plan.details,
imagePath: plan.plan.image,
),
const SizedBox(height: 8),
OptionWidget(
option: chosenOption,
disableDuration: true,
),
const SizedBox(height: 8),
PlanState(
state: plan.chosenPlanState,
),
const SizedBox(height: 30),
],
),
));
},
);
}
}

View File

@@ -0,0 +1,161 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
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 PaymentBanner extends StatefulWidget {
const PaymentBanner({
super.key,
required this.successfulPayment,
});
final bool successfulPayment;
@override
State<PaymentBanner> createState() => _PaymentBannerState();
}
class _PaymentBannerState extends State<PaymentBanner> {
bool isLoading = false;
@override
Widget build(BuildContext context) {
final s = S.of(context);
final size = MediaQuery.sizeOf(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: widget.successfulPayment ? appTheme.blueA400 : appTheme.strongRed,
borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20))),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: widget.successfulPayment
? [
SizedBox(
width: size.width * 0.6,
child: Text(
s.cunsutation_was_paid_successfully,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 8,
),
SizedBox(
width: size.width * 0.6,
child: Text(
s.wait_for_start_day,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 16,
),
isLoading
? const Center(child: Loader())
: CustomOutlinedButton(
text: s.go_to_diary,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle:
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
onPressed: () async {
GoRouter.of(context).pushReplacement('/diary-screen');
},
),
isLoading
? const Center(child: Loader())
: CustomOutlinedButton(
text: s.back_to_chat,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle:
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
onPressed: () {
GoRouter.of(context).pop();
},
)
]
: [
SizedBox(
width: size.width * 0.6,
child: Text(
s.smth_went_wrong_payment_failed,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 8,
),
SizedBox(
width: size.width * 0.6,
child: Text(
s.try_again_2,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black),
),
),
const SizedBox(
height: 16,
),
isLoading
? const Center(child: Loader())
: CustomOutlinedButton(
text: s.pay,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle:
ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)),
onPressed: () async {
final planId = context.read<OrderInfoCubit>().state.chosenPlan?.id;
setState(() {
isLoading = true;
});
final paymentLink = await context.read<OrderInfoCubit>().getPaymentLink();
setState(() {
isLoading = false;
});
if (paymentLink != null && planId != null) {
if (!context.mounted) return;
GoRouter.of(context).pushReplacement(
PaymentScreen.path,
extra: {'url': paymentLink, 'planId': planId},
);
}
},
)
],
),
)
])
// ? Column(children: [
// SizedBox(
// width: size.width * 0.6,
// child: Text(
// s.smth_went_wrong_payment_failed,
// style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
// ),
// ),
// ])
// : Padding(
// padding: const EdgeInsets.only(right: 16),
// child: ,
// ),
);
}
}

View File

@@ -59,6 +59,7 @@ class MessageLookup extends MessageLookupByLibrary {
"average_pressure": MessageLookupByLibrary.simpleMessage(
"Средние показатели давления:",
),
"back_to_chat": MessageLookupByLibrary.simpleMessage("Вернуться в чат"),
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
"by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"),
"calls_for_bullying": MessageLookupByLibrary.simpleMessage(
@@ -71,6 +72,9 @@ class MessageLookup extends MessageLookupByLibrary {
"can_not_load_data_please_try_again": MessageLookupByLibrary.simpleMessage(
"Не удалось загрузить данные. Попробуйте обновить страницу",
),
"can_not_see_next_day": MessageLookupByLibrary.simpleMessage(
"Просмотр последующего дня возможен только при прохождении диеты",
),
"cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"),
"cancel_order": MessageLookupByLibrary.simpleMessage("Отменить заказ"),
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
@@ -166,6 +170,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
"create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"),
"cunsutation_was_paid_successfully": MessageLookupByLibrary.simpleMessage(
"Поздравляем, консультация успешно оплачена",
),
"customer_price": MessageLookupByLibrary.simpleMessage(
"Цена для покупателя",
),
@@ -285,6 +292,7 @@ class MessageLookup extends MessageLookupByLibrary {
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
"general_info": MessageLookupByLibrary.simpleMessage("Общая информация"),
"go_to_diary": MessageLookupByLibrary.simpleMessage("Перейти в дневник"),
"gr": MessageLookupByLibrary.simpleMessage("гр"),
"grams": MessageLookupByLibrary.simpleMessage("гр"),
"has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"),
@@ -508,6 +516,9 @@ class MessageLookup extends MessageLookupByLibrary {
"slim_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
),
"smth_went_wrong_payment_failed": MessageLookupByLibrary.simpleMessage(
"Что-то пошло не так, оплата не прошла",
),
"some_error_occurred": MessageLookupByLibrary.simpleMessage(
"Упс! Произошла ошибка",
),
@@ -517,6 +528,9 @@ class MessageLookup extends MessageLookupByLibrary {
"something_went_wrong": MessageLookupByLibrary.simpleMessage(
"Упс! Что-то пошло не так",
),
"sorry_we_did_not_find_the_plan": MessageLookupByLibrary.simpleMessage(
"Извините, план где-то потерялся, мы уже ищем его",
),
"spam": MessageLookupByLibrary.simpleMessage("Спам"),
"spam_description": MessageLookupByLibrary.simpleMessage(
"Отправьте жалобу, если Вы заметили рекламу, размещенную в не предназначенном для этого месте.",
@@ -557,6 +571,7 @@ class MessageLookup extends MessageLookupByLibrary {
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
"to_order": MessageLookupByLibrary.simpleMessage("Заказать"),
"try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"),
"try_again_2": MessageLookupByLibrary.simpleMessage("Попробуйте ещё раз"),
"type_here_your_complaint": MessageLookupByLibrary.simpleMessage(
"Напишите здесь свою жалобу",
),
@@ -589,6 +604,12 @@ class MessageLookup extends MessageLookupByLibrary {
),
"waist": MessageLookupByLibrary.simpleMessage("Талия"),
"waist_circumference": MessageLookupByLibrary.simpleMessage("Обхват талии"),
"wait_for_start_day": MessageLookupByLibrary.simpleMessage(
"Ожидайте начала плана питания",
),
"want_to_see_tomorrow": MessageLookupByLibrary.simpleMessage(
"Хотите заглянуть в завтра?",
),
"weapon": MessageLookupByLibrary.simpleMessage("Оружие"),
"weight": MessageLookupByLibrary.simpleMessage("Вес"),
"weight_chart": MessageLookupByLibrary.simpleMessage("График веса"),

View File

@@ -2754,6 +2754,96 @@ class S {
String get done {
return Intl.message('Завершен', name: 'done', desc: '', args: []);
}
/// `Поздравляем, консультация успешно оплачена`
String get cunsutation_was_paid_successfully {
return Intl.message(
'Поздравляем, консультация успешно оплачена',
name: 'cunsutation_was_paid_successfully',
desc: '',
args: [],
);
}
/// `Ожидайте начала плана питания`
String get wait_for_start_day {
return Intl.message(
'Ожидайте начала плана питания',
name: 'wait_for_start_day',
desc: '',
args: [],
);
}
/// `Перейти в дневник`
String get go_to_diary {
return Intl.message(
'Перейти в дневник',
name: 'go_to_diary',
desc: '',
args: [],
);
}
/// `Вернуться в чат`
String get back_to_chat {
return Intl.message(
'Вернуться в чат',
name: 'back_to_chat',
desc: '',
args: [],
);
}
/// `Что-то пошло не так, оплата не прошла`
String get smth_went_wrong_payment_failed {
return Intl.message(
'Что-то пошло не так, оплата не прошла',
name: 'smth_went_wrong_payment_failed',
desc: '',
args: [],
);
}
/// `Попробуйте ещё раз`
String get try_again_2 {
return Intl.message(
'Попробуйте ещё раз',
name: 'try_again_2',
desc: '',
args: [],
);
}
/// `Извините, план где-то потерялся, мы уже ищем его`
String get sorry_we_did_not_find_the_plan {
return Intl.message(
'Извините, план где-то потерялся, мы уже ищем его',
name: 'sorry_we_did_not_find_the_plan',
desc: '',
args: [],
);
}
/// `Просмотр последующего дня возможен только при прохождении диеты`
String get can_not_see_next_day {
return Intl.message(
'Просмотр последующего дня возможен только при прохождении диеты',
name: 'can_not_see_next_day',
desc: '',
args: [],
);
}
/// `Хотите заглянуть в завтра?`
String get want_to_see_tomorrow {
return Intl.message(
'Хотите заглянуть в завтра?',
name: 'want_to_see_tomorrow',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -337,7 +337,16 @@
"time_to_complete_plan": "Время выполнения плана",
"order_created": "Заказ создан",
"startDateHasBeenSet": "Выбрана дата старта",
"done": "Завершен"
"done": "Завершен",
"cunsutation_was_paid_successfully": "Поздравляем, консультация успешно оплачена",
"wait_for_start_day": "Ожидайте начала плана питания",
"go_to_diary": "Перейти в дневник",
"back_to_chat": "Вернуться в чат",
"smth_went_wrong_payment_failed": "Что-то пошло не так, оплата не прошла",
"try_again_2": "Попробуйте ещё раз",
"sorry_we_did_not_find_the_plan": "Извините, план где-то потерялся, мы уже ищем его",
"can_not_see_next_day": "Просмотр последующего дня возможен только при прохождении диеты",
"want_to_see_tomorrow": "Хотите заглянуть в завтра?"
}

View File

@@ -1,10 +1,10 @@
name: eatvio_app
description: A new Flutter project.
version: 1.0.50+5
version: 1.0.50+6
publish_to: 'none'
environment:
sdk: ^3.7.0
sdk: '>=3.3.4 <4.0.0'
dependencies:
flutter: