From 6fa4bffd7ef2e3ca678c11300928450b6824774c Mon Sep 17 00:00:00 2001 From: Username1548 Date: Thu, 5 Jun 2025 22:42:50 +0700 Subject: [PATCH 1/6] feat(consultantFinaces): added consultant finaces screen --- lib/core/app_router.dart | 10 +- lib/core/locator_service.dart | 2 + .../pages/consultations_screen.dart | 3 +- .../consultations_repository.dart | 9 + .../services/consultation_service.dart | 20 + .../consultant_finances_cubit.dart | 16 +- .../consultant_finances_state.dart | 3 +- .../models/consultant_finance_model.dart | 36 ++ .../consultant_finances_screen.dart | 491 ++++++++---------- .../consultations_archive_screen.dart | 3 +- .../menu/presentation/pages/menu_screen.dart | 6 +- lib/main.dart | 2 +- 12 files changed, 309 insertions(+), 292 deletions(-) create mode 100644 lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index c849628d..0f0ddf45 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -16,6 +16,7 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart'; import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart'; @@ -181,10 +182,15 @@ class AppRouter { }, ), GoRoute( - path: '/consultant-finance-screen', - name: 'consultantFinanceScreen', + path: ConsultationsArchiveScreen.path, + name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen(), ), + GoRoute( + path: ConsultantFinancesScreen.path, + name: ConsultantFinancesScreen.name, + builder: (context, state) => const ConsultantFinancesScreen(), + ), GoRoute( path: '/user-finance-screen', name: 'userFinanceScreen', diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index cd8731a3..47a14057 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -30,6 +30,7 @@ import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart'; import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart'; @@ -147,6 +148,7 @@ Future init() async { sl.registerFactory(() => VerifyEmailCubit(profileRepository: sl())); sl.registerFactory(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl())); sl.registerFactory(() => ConsultationsArchiveCubit(consultationsRepository: sl())); + sl.registerFactory(() => ConsultantFinancesCubit(consultationsRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/features/consultations/presentation/pages/consultations_screen.dart b/lib/features/consultations/presentation/pages/consultations_screen.dart index d6acb306..6f0021d9 100644 --- a/lib/features/consultations/presentation/pages/consultations_screen.dart +++ b/lib/features/consultations/presentation/pages/consultations_screen.dart @@ -6,6 +6,7 @@ import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart'; import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart'; import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -67,7 +68,7 @@ class ConsultationsContent extends StatelessWidget { InkWell( borderRadius: BorderRadius.circular(20), onTap: () { - GoRouter.of(context).pushNamed('consultantFinanceScreen'); + GoRouter.of(context).pushNamed(ConsultationsArchiveScreen.name); }, child: Ink( width: MediaQuery.of(context).size.width - 32, diff --git a/lib/features/consultations/repositories/consultations_repository.dart b/lib/features/consultations/repositories/consultations_repository.dart index 3ebcef87..385c851b 100644 --- a/lib/features/consultations/repositories/consultations_repository.dart +++ b/lib/features/consultations/repositories/consultations_repository.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/consultations/services/consultation_service.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart'; class ConsultationsRepository { @@ -42,4 +43,12 @@ class ConsultationsRepository { rethrow; } } + + Future> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async { + try { + return await consultationService.getConsultantFinances(date, dateComparison); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/consultations/services/consultation_service.dart b/lib/features/consultations/services/consultation_service.dart index fa14b69c..01a64b11 100644 --- a/lib/features/consultations/services/consultation_service.dart +++ b/lib/features/consultations/services/consultation_service.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart'; class ConsultationService { @@ -66,4 +67,23 @@ class ConsultationService { rethrow; } } + + Future> getConsultantFinances( + String? date, + FiltersComparisonEnum? dateComparison, + ) async { + try { + final response = await dio.get( + '/api/finances/my_finances', + queryParameters: { + if (dateComparison != null && date != null) 'filters[end_date]': date, + }, + ); + final data = response.data as Map; + final consultations = data['data'] as List; + return consultations.map((consultation) => ConsultantFinanceModel.fromJson(consultation as Map)).toList(); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart b/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart index 1324da92..69d25052 100644 --- a/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart +++ b/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; @@ -13,19 +14,22 @@ class ConsultantFinancesCubit extends Cubit { ConsultantFinancesState(), ); - Future getConsultations() async { + Future getConsultantFinances() async { try { emit(state.copyWith(isLoading: true)); + final consultantFinances = await consultationsRepository.getConsultantFinances( + state.periodFilterStatus?.value, + state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, + ); + emit(state.copyWith(financesModels: consultantFinances, isLoading: false)); } catch (e) { - emit(state.copyWith(isLoading: false)); + emit(state.copyWith(financesModels: [], isLoading: false)); log(e.toString()); } } - Future setConsultationsPeriod(PeriodFilterStatus period) async { + Future setFinancesPeriod(PeriodFilterStatus period) async { emit(state.copyWith(periodFilterStatus: period)); - await getConsultations(); + await getConsultantFinances(); } } - - diff --git a/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart b/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart index 9692d4a6..fff61e45 100644 --- a/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart +++ b/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -6,7 +7,7 @@ part 'consultant_finances_state.freezed.dart'; @freezed abstract class ConsultantFinancesState with _$ConsultantFinancesState { factory ConsultantFinancesState({ - @Default([]) List financesModels, + @Default([]) List financesModels, @Default(false) isLoading, @Default(null) String? error, @Default(null) PeriodFilterStatus? periodFilterStatus, diff --git a/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart b/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart new file mode 100644 index 00000000..41161f22 --- /dev/null +++ b/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart @@ -0,0 +1,36 @@ +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'consultant_finance_model.freezed.dart'; +part 'consultant_finance_model.g.dart'; + +@freezed +abstract class ConsultantFinanceModel with _$ConsultantFinanceModel { + factory ConsultantFinanceModel({ + @JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate, + @JsonKey(name: 'bank_id') required String bankId, + @JsonKey(name: 'title') required String title, + @JsonKey(name: 'clientName') required String clientName, + @JsonKey(name: 'duration') required int duration, + @JsonKey(name: 'payment_price', fromJson: _parsePayment) required String paymentPrice, + }) = _ConsultantFinanceModel; + + factory ConsultantFinanceModel.fromJson(Map json) => _$ConsultantFinanceModelFromJson(json); +} + +String _parsePayment(json) { + return ((json as int) / 100).toStringAsFixed(2); +} + +enum PaymentStatus { + @JsonValue('COMPLETED') + completed, + @JsonValue('REJECTED') + rejected, + @JsonValue('CHECKED') + checked, + @JsonValue('CREDIT_CHECKING') + creditChecking, + @JsonValue('COMPLETING') + completing; +} diff --git a/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart b/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart index bb225221..6c1e93f6 100644 --- a/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart +++ b/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart @@ -1,281 +1,214 @@ -// import 'package:eatvio_app/core/locator_service.dart'; -// import 'package:eatvio_app/core/theme/custom_text_style.dart'; -// import 'package:eatvio_app/core/theme/theme_helper.dart'; -// import 'package:eatvio_app/core/utils/date_time_utils.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/core/widgets/loader.dart'; -// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart'; -// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_state.dart'; -// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart'; -// import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart'; -// import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; -// import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart'; -// import 'package:eatvio_app/generated/l10n.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_bloc/flutter_bloc.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/utils/date_time_utils.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/core/widgets/loader.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.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'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; -// class ConsultantFinancesScreen extends StatefulWidget { -// const ConsultantFinancesScreen({super.key}); +class ConsultantFinancesScreen extends StatefulWidget { + const ConsultantFinancesScreen({super.key}); + static const name = 'consultantFinancesScreen'; + static const path = '/consultant-finances-screen'; + @override + State createState() => _ConsultantFinancesScreenState(); +} -// @override -// State createState() => _ConsultantFinancesScreenState(); -// } - -// class _ConsultantFinancesScreenState extends State { -// @override -// Widget build(BuildContext context) { -// final s = S.of(context); -// return BlocProvider( -// create: (context) => sl(), -// child: BlocBuilder( -// builder: (context, state) { -// return Scaffold( -// backgroundColor: const Color(0xFFF0F2F5), -// appBar: AppBar( -// shape: const RoundedRectangleBorder( -// borderRadius: BorderRadiusDirectional.vertical( -// bottom: Radius.circular(12), -// ), -// ), -// title: const Text('Архив консультаций'), -// centerTitle: true, -// ), -// body: state.isLoading -// ? const Center( -// child: Loader(), -// ) -// : SingleChildScrollView( -// padding: const EdgeInsets.symmetric(vertical: 8), -// child: Column( -// children: [ -// Material( -// color: Colors.white, -// borderRadius: BorderRadius.circular(12), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// const SizedBox(height: 16), -// Padding( -// padding: const EdgeInsets.symmetric(horizontal: 16), -// child: Text( -// 'Всего консультаций: ${state.statisticModel.total}', -// style: const TextStyle( -// fontSize: 16, -// fontWeight: FontWeight.w500, -// color: Color(0xFF6D6F71), -// height: 1, -// ), -// ), -// ), -// const SizedBox(height: 12), -// Row( -// children: [ -// const SizedBox(width: 16), -// Material( -// color: const Color(0xFF0BB450).withAlpha(11), -// borderRadius: BorderRadius.circular(8), -// child: Padding( -// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), -// child: Text( -// 'Завершено: ${state.statisticModel.finished}', -// style: const TextStyle( -// fontSize: 14, -// fontWeight: FontWeight.w400, -// color: Color(0xFF0BB450), -// height: 1, -// ), -// ), -// ), -// ), -// const SizedBox(width: 8), -// Material( -// color: const Color(0xFFD01717).withAlpha(11), -// borderRadius: BorderRadius.circular(8), -// child: Padding( -// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), -// child: Text( -// 'Отменено: ${state.statisticModel.canceled}', -// style: const TextStyle( -// fontSize: 14, -// fontWeight: FontWeight.w400, -// color: Color(0xFFD01717), -// height: 1, -// ), -// ), -// ), -// ), -// ], -// ), -// const SizedBox(height: 16), -// ], -// ), -// ), -// const SizedBox(height: 8), -// Material( -// color: Colors.white, -// borderRadius: BorderRadius.circular(12), -// child: Padding( -// padding: const EdgeInsets.only(left: 16, right: 16, top: 16), -// child: SingleChildScrollView( -// physics: const NeverScrollableScrollPhysics(), -// child: Column( -// children: [ -// CustomDropDownButton( -// items: PeriodFilterStatus.values -// .map( -// (value) => DropdownMenuItem( -// value: value, -// child: Text( -// value.name, -// style: TextStyles.textMMedium.copyWith(color: appTheme.black), -// ), -// ), -// ) -// .toList(), -// onChanged: (value) { -// if (value == null) return; -// context.read().setConsultationsPeriod(value); -// }, -// value: state.periodFilterStatus ?? PeriodFilterStatus.values.first, -// ), -// const SizedBox(height: 12), -// CustomDropDownButton( -// items: ConsultationFilterStatus.values -// .map( -// (value) => DropdownMenuItem( -// value: value, -// child: Text( -// value.name, -// style: TextStyles.textMMedium.copyWith(color: appTheme.black), -// ), -// ), -// ) -// .toList(), -// onChanged: (value) { -// if (value == null) return; -// context.read().setConsultationsStatus(value); -// }, -// value: state.consultationFilterStatus, -// ), -// const SizedBox(height: 12), -// if (state.consultations.isNotEmpty) -// ListView.separated( -// shrinkWrap: true, -// physics: const NeverScrollableScrollPhysics(), -// itemBuilder: (context, index) { -// return Material( -// color: const Color(0xFFF6F6F6), -// borderRadius: BorderRadius.circular(20), -// child: Padding( -// padding: const EdgeInsets.all(12), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Row( -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// Text( -// state.consultations[index].id.toString(), -// style: const TextStyle( -// color: Color(0xFF323F4B), -// height: 1, -// fontWeight: FontWeight.w500, -// ), -// ), -// Text( -// state.consultations[index].startDate.format('yyyy.MM.dd'), -// style: const TextStyle( -// color: Color(0xFF6D6F71), -// height: 1, -// fontWeight: FontWeight.w500, -// ), -// ), -// ], -// ), -// const SizedBox(height: 8), -// Text( -// state.consultations[index].planTitle, -// style: TextStyle( -// color: appTheme.blueA400, -// height: 1, -// fontSize: 18, -// fontWeight: FontWeight.w600, -// ), -// ), -// const SizedBox(height: 8), -// Text( -// 'Покупатель: ${state.consultations[index].userName}', -// style: const TextStyle( -// color: Color(0xFF323F4B), -// height: 1, -// fontSize: 14, -// fontWeight: FontWeight.w500, -// ), -// ), -// const SizedBox(height: 8), -// Text( -// '${state.consultations[index].duration} ${getDayForm(state.consultations[index].duration, s)}', -// style: const TextStyle( -// color: Color(0xFF323F4B), -// height: 1, -// fontSize: 14, -// fontWeight: FontWeight.w500, -// ), -// ), -// const SizedBox(height: 8), -// Container( -// height: 1, -// width: double.infinity, -// decoration: const BoxDecoration( -// color: Color(0xFFDEDEDF), -// ), -// ), -// const SizedBox(height: 8), -// Row( -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// Text( -// '${state.consultations[index].consultantPayment.toStringAsFixed(2)} ₽', -// style: const TextStyle( -// color: Color(0xFF323F4B), -// height: 1, -// fontSize: 18, -// fontWeight: FontWeight.w600, -// ), -// ), -// StatusWidget( -// status: state.consultations[index].chosenPlanState, -// ), -// ], -// ), -// ], -// ), -// ), -// ); -// }, -// separatorBuilder: (context, index) => const SizedBox(height: 12), -// itemCount: state.financesModels.length, -// ) -// else -// const Padding( -// padding: EdgeInsets.symmetric(vertical: 40.0), -// child: Text( -// 'У вас нет таких консультаций', -// style: TextStyles.textMMedium, -// ), -// ), -// ], -// ), -// ), -// ), -// ), -// ], -// ), -// ), -// ); -// }, -// ), -// ); -// } -// } +class _ConsultantFinancesScreenState extends State { + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocProvider( + create: (context) => sl()..getConsultantFinances(), + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: const Color(0xFFF0F2F5), + appBar: AppBar( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.vertical( + bottom: Radius.circular(12), + ), + ), + title: const Text('Архив консультаций'), + centerTitle: true, + ), + body: state.isLoading + ? const Center( + child: Loader(), + ) + : SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + children: [ + InkWell( + onTap: () { + GoRouter.of(context).pushNamed(CardSettingsScreen.name); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400), + width: MediaQuery.of(context).size.width - 32, + child: const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: Text('Управление картами', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)), + ), + ), + ), + ), + const SizedBox(height: 8), + Material( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 16), + child: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Column( + children: [ + CustomDropDownButton( + items: PeriodFilterStatus.values + .map( + (value) => DropdownMenuItem( + value: value, + child: Text( + value.name, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + ) + .toList(), + onChanged: (value) { + if (value == null) return; + context.read().setFinancesPeriod(value); + }, + value: state.periodFilterStatus ?? PeriodFilterStatus.values.first, + ), + const SizedBox(height: 12), + if (state.financesModels.isNotEmpty) + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Material( + color: const Color(0xFFF6F6F6), + borderRadius: BorderRadius.circular(20), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Builder(builder: (context) { + final idLen = state.financesModels[index].bankId.length; + return Text( + '${state.financesModels[index].bankId.substring(0, 4)}...${state.financesModels[index].bankId.substring(idLen - 4, idLen)} ', + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ); + }), + Text( + state.financesModels[index].startDate.format('yyyy.MM.dd'), + style: const TextStyle( + color: Color(0xFF6D6F71), + height: 1, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + state.financesModels[index].title, + style: TextStyle( + color: appTheme.blueA400, + height: 1, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Text( + 'Покупатель: ${state.financesModels[index].clientName}', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + '${state.financesModels[index].duration} ${getDayForm(state.financesModels[index].duration, s)}', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Container( + height: 1, + width: double.infinity, + decoration: const BoxDecoration( + color: Color(0xFFDEDEDF), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${state.financesModels[index].paymentPrice} ₽', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + // StatusWidget( + // status: state.consultations[index].chosenPlanState, + // ), + ], + ), + ], + ), + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemCount: state.financesModels.length, + ) + else + const Padding( + padding: EdgeInsets.symmetric(vertical: 40.0), + child: Text( + 'У вас нет таких консультаций', + style: TextStyles.textMMedium, + ), + ), + ], + ), + ), + ), + ), + ], + )), + ); + }, + ), + ); + } +} diff --git a/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart b/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart index fa934bcf..9bdecd59 100644 --- a/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart +++ b/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart @@ -15,7 +15,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class ConsultationsArchiveScreen extends StatefulWidget { const ConsultationsArchiveScreen({super.key}); - + static const name = 'consultationsArchiveScreen'; + static const path = '/consultations-archive-screen'; @override State createState() => _ConsultationsArchiveScreenState(); } diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index cb2ff7ff..a20f34d0 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart'; import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; @@ -40,7 +41,10 @@ class MenuScreen extends StatelessWidget { leading: 'assets/icons/finance.svg', title: 'Мои финансы', onTap: () { - GoRouter.of(context).pushNamed('userFinanceScreen'); + if (profileModel.roles.contains('consultant')) { + GoRouter.of(context).push(ConsultantFinancesScreen.path); + return; + } }, ), _divider(context), diff --git a/lib/main.dart b/lib/main.dart index d7d37793..cb39ac46 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,7 +23,7 @@ Future main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - await di.init(); + await di.init(); await SentryFlutter.init((options) { options.dsn = 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2'; options.tracesSampleRate = 1.0; From 483cf6d8eeac628fbe79be4f0d05f2e9ce3f0f76 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 9 Jun 2025 14:01:05 +0700 Subject: [PATCH 2/6] feat(userPurchases): added user purchases, fix(consultations): fixed consultations bug --- lib/core/app_router.dart | 6 + lib/core/locator_service.dart | 2 + .../widgets/consultation_card_widget.dart | 4 +- .../consultations_repository.dart | 11 +- .../services/consultation_service.dart | 20 ++ .../user_purchases/user_purchases_cubit.dart | 36 ++++ .../user_purchases/user_purchases_state.dart | 15 ++ .../models/user_purchases_model.dart | 19 ++ .../consultant_finances_screen.dart | 5 +- .../presentation/user_purchases_screen.dart | 200 ++++++++++++++++++ .../menu/presentation/pages/menu_screen.dart | 3 + lib/main.dart | 4 +- 12 files changed, 320 insertions(+), 5 deletions(-) create mode 100644 lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart create mode 100644 lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart create mode 100644 lib/features/finances_and_consultations_archive/models/user_purchases_model.dart create mode 100644 lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 0f0ddf45..dfc2101c 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -17,6 +17,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/presentation/pa import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart'; import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart'; import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart'; @@ -191,6 +192,11 @@ class AppRouter { name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen(), ), + GoRoute( + path: UserPurchasesScreen.path, + name: UserPurchasesScreen.name, + builder: (context, state) => const UserPurchasesScreen(), + ), GoRoute( path: '/user-finance-screen', name: 'userFinanceScreen', diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 47a14057..bd2dd469 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -31,6 +31,7 @@ import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repo import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart'; @@ -149,6 +150,7 @@ Future init() async { sl.registerFactory(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl())); sl.registerFactory(() => ConsultationsArchiveCubit(consultationsRepository: sl())); sl.registerFactory(() => ConsultantFinancesCubit(consultationsRepository: sl())); + sl.registerFactory(() => UserPurchasesCubit(consultationsRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart index c9b9f8b2..11322228 100644 --- a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart +++ b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart @@ -185,6 +185,8 @@ class _ConsultationCardWidgetState extends State { children: List.generate(widget.consultationsModel.duration, (index) { final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index); final hasActivity = day != null && day.$2.state != DiaryStateEnum.init; + final canConsultantOpenThisDay = day != null && + (day.$2.state != DiaryStateEnum.init && day.$2.state != DiaryStateEnum.started && day.$2.state != DiaryStateEnum.lastDayStarted); return Padding( padding: const EdgeInsets.only(right: 12), child: Column( @@ -192,7 +194,7 @@ class _ConsultationCardWidgetState extends State { children: [ InkWell( onTap: () { - if (!hasActivity) { + if (!hasActivity || !canConsultantOpenThisDay) { showErrorDialog(context, title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}'); return; diff --git a/lib/features/consultations/repositories/consultations_repository.dart b/lib/features/consultations/repositories/consultations_repository.dart index 385c851b..15490dc9 100644 --- a/lib/features/consultations/repositories/consultations_repository.dart +++ b/lib/features/consultations/repositories/consultations_repository.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/features/consultations/models/consultation_model.dart import 'package:eatvio_app/features/consultations/services/consultation_service.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart'; class ConsultationsRepository { final ConsultationService consultationService; @@ -45,10 +46,18 @@ class ConsultationsRepository { } Future> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async { - try { + try { return await consultationService.getConsultantFinances(date, dateComparison); } catch (e) { rethrow; } } + + Future> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async { + try { + return await consultationService.getUserPurchases(date, dateComparison); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/consultations/services/consultation_service.dart b/lib/features/consultations/services/consultation_service.dart index 01a64b11..f0a89220 100644 --- a/lib/features/consultations/services/consultation_service.dart +++ b/lib/features/consultations/services/consultation_service.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart'; class ConsultationService { final Dio dio; @@ -86,4 +87,23 @@ class ConsultationService { rethrow; } } + + Future> getUserPurchases( + String? date, + FiltersComparisonEnum? dateComparison, + ) async { + try { + final response = await dio.get( + '/api/finances/my_purchases', + queryParameters: { + if (dateComparison != null && date != null) 'filters[start_date]': date, + }, + ); + final data = response.data as Map; + final purchases = data['data'] as List; + return purchases.map((purchase) => UserPurchasesModel.fromJson(purchase as Map)).toList(); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart b/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart new file mode 100644 index 00000000..55fdbc68 --- /dev/null +++ b/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart @@ -0,0 +1,36 @@ +import 'dart:developer'; + +import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; +import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class UserPurchasesCubit extends Cubit { + final ConsultationsRepository consultationsRepository; + UserPurchasesCubit({ + required this.consultationsRepository, + }) : super( + UserPurchasesState(), + ); + + Future getUserPurchases() async { + try { + emit(state.copyWith(isLoading: true)); + final purchasesModels = await consultationsRepository.getUserPurchases( + state.periodFilterStatus?.value, + state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, + ); + + emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false)); + } catch (e) { + emit(state.copyWith(purchasesModels: [], isLoading: false)); + log(e.toString()); + } + } + + Future setPurchasesPeriod(PeriodFilterStatus period) async { + emit(state.copyWith(periodFilterStatus: period)); + await getUserPurchases(); + } +} diff --git a/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart b/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart new file mode 100644 index 00000000..7ae22c03 --- /dev/null +++ b/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart @@ -0,0 +1,15 @@ +import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_purchases_state.freezed.dart'; + +@freezed +abstract class UserPurchasesState with _$UserPurchasesState { + factory UserPurchasesState({ + @Default([]) List purchasesModels, + @Default(false) isLoading, + @Default(null) String? error, + @Default(null) PeriodFilterStatus? periodFilterStatus, + }) = _UserPurchasesState; +} diff --git a/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart b/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart new file mode 100644 index 00000000..deaf42dc --- /dev/null +++ b/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart @@ -0,0 +1,19 @@ +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_purchases_model.freezed.dart'; +part 'user_purchases_model.g.dart'; + +@freezed +abstract class UserPurchasesModel with _$UserPurchasesModel { + factory UserPurchasesModel({ + @JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate, + @JsonKey(name: 'bank_id') required String? bankId, + @JsonKey(name: 'title') required String title, + @JsonKey(name: 'consultantName') required String consultantName, + @JsonKey(name: 'duration') required int duration, + @JsonKey(name: 'price') required int paymentPrice, + }) = _UserPurchasesModel; + + factory UserPurchasesModel.fromJson(Map json) => _$UserPurchasesModelFromJson(json); +} \ No newline at end of file diff --git a/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart b/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart index 6c1e93f6..9d1769c4 100644 --- a/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart +++ b/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart @@ -38,7 +38,10 @@ class _ConsultantFinancesScreenState extends State { bottom: Radius.circular(12), ), ), - title: const Text('Архив консультаций'), + title: Text( + 'Финансы', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), centerTitle: true, ), body: state.isLoading diff --git a/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart b/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart new file mode 100644 index 00000000..e1f9f036 --- /dev/null +++ b/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart @@ -0,0 +1,200 @@ +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.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/core/widgets/loader.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class UserPurchasesScreen extends StatefulWidget { + const UserPurchasesScreen({super.key}); + static const name = 'userPurchasesScreen'; + static const path = '/user-purchases-screen'; + @override + State createState() => _UserPurchasesScreen(); +} + +class _UserPurchasesScreen extends State { + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocProvider( + create: (context) => sl()..getUserPurchases(), + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: const Color(0xFFF0F2F5), + appBar: AppBar( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.vertical( + bottom: Radius.circular(12), + ), + ), + title: Text( + 'Финансы', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + centerTitle: true, + ), + body: state.isLoading + ? const Center( + child: Loader(), + ) + : SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + children: [ + const SizedBox(height: 8), + Material( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 16), + child: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Column( + children: [ + CustomDropDownButton( + items: PeriodFilterStatus.values + .map( + (value) => DropdownMenuItem( + value: value, + child: Text( + value.name, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + ) + .toList(), + onChanged: (value) { + if (value == null) return; + context.read().setPurchasesPeriod(value); + }, + value: state.periodFilterStatus ?? PeriodFilterStatus.values.first, + ), + const SizedBox(height: 12), + if (state.purchasesModels.isNotEmpty) + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Material( + color: const Color(0xFFF6F6F6), + borderRadius: BorderRadius.circular(20), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Builder(builder: (context) { + // final idLen = state.purchasesModels[index].bankId.length; + final bankId = state.purchasesModels[index].bankId; + return Text( + '$bankId', + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ); + }), + Text( + state.purchasesModels[index].startDate.format('yyyy.MM.dd'), + style: const TextStyle( + color: Color(0xFF6D6F71), + height: 1, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + state.purchasesModels[index].title, + style: TextStyle( + color: appTheme.blueA400, + height: 1, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Text( + 'Продавец: ${state.purchasesModels[index].consultantName}', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + '${state.purchasesModels[index].duration} ${getDayForm(state.purchasesModels[index].duration, s)}', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Container( + height: 1, + width: double.infinity, + decoration: const BoxDecoration( + color: Color(0xFFDEDEDF), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${state.purchasesModels[index].paymentPrice} ₽', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + // StatusWidget( + // status: state.consultations[index].chosenPlanState, + // ), + ], + ), + ], + ), + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemCount: state.purchasesModels.length, + ) + else + const Padding( + padding: EdgeInsets.symmetric(vertical: 40.0), + child: Text( + 'У вас нет таких консультаций', + style: TextStyles.textMMedium, + ), + ), + ], + ), + ), + ), + ), + ], + )), + ); + }, + ), + ); + } +} diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index a20f34d0..8b3506ed 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -5,6 +5,7 @@ import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart'; import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart'; import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; @@ -43,8 +44,10 @@ class MenuScreen extends StatelessWidget { onTap: () { if (profileModel.roles.contains('consultant')) { GoRouter.of(context).push(ConsultantFinancesScreen.path); + GoRouter.of(context).push(UserPurchasesScreen.path); return; } + GoRouter.of(context).push(UserPurchasesScreen.path); }, ), _divider(context), diff --git a/lib/main.dart b/lib/main.dart index cb39ac46..b4eaba75 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,9 +21,9 @@ import 'package:sentry_flutter/sentry_flutter.dart'; Future main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - await di.init(); + await di.init(); await SentryFlutter.init((options) { options.dsn = 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2'; options.tracesSampleRate = 1.0; From 2584a70a093b8cad838d8b1d1da97e11474a875a Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 9 Jun 2025 19:51:32 +0700 Subject: [PATCH 3/6] fix(diary): fixed app review issues --- lib/core/const.dart | 7 +- lib/core/locator_service.dart | 10 +- .../diary/presentation/bloc/diary_cubit.dart | 24 ++-- .../diary/presentation/bloc/diary_state.dart | 2 + .../presentation/pages/diary_screen.dart | 22 ++-- .../widgets/calories_chart_widget.dart | 2 +- .../presentation/widgets/meal_widget.dart | 1 - .../widgets/wakeup_and_retire_widget.dart | 111 +++++++++--------- 8 files changed, 102 insertions(+), 77 deletions(-) diff --git a/lib/core/const.dart b/lib/core/const.dart index 31c47e9c..51e7feff 100644 --- a/lib/core/const.dart +++ b/lib/core/const.dart @@ -1,5 +1,8 @@ -String baseUrl = 'https://eatvio.ru'; -// String baseUrl = 'https://dev2.eatvio.com'; +void qwe() { + print('change to prod url'); +} +// String baseUrl = 'https://eatvio.ru'; +String baseUrl = 'https://dev2.eatvio.com'; const List durationValues = [7, 14, 21]; diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index cd8731a3..cdf658ca 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -122,8 +122,14 @@ Future init() async { sl.registerFactory(() => PlansCubit(feedRepository: sl(), planRepository: sl())); sl.registerFactory(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl())); sl.registerFactory(() => RegisterConsultantCubit(consultantRepository: sl())); - sl.registerFactory( - () => DiaryCubit(chatRepository: sl(), diaryRepository: sl(), glossaryRepository: sl(), profileRepository: sl(), dietRepository: sl())); + sl.registerFactory(() => DiaryCubit( + chatRepository: sl(), + diaryRepository: sl(), + glossaryRepository: sl(), + profileRepository: sl(), + dietRepository: sl(), + settingsRepository: sl(), + )); sl.registerFactory(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl())); sl.registerFactory(() => BlogPostCreateCubit(blogPostRepository: sl())); sl.registerFactory(() => SubCubit(profileRepository: sl())); diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index afe81398..7216ed1c 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -14,6 +14,7 @@ import 'package:eatvio_app/features/diets/repositories/diet_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:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class DiaryCubit extends Cubit { @@ -25,14 +26,16 @@ class DiaryCubit extends Cubit { final ProfileRepository profileRepository; final DietRepository dietRepository; final ChatRepository chatRepository; + final SettingsRepository settingsRepository; int? diaryId; - DiaryCubit( - {required this.chatRepository, - required this.diaryRepository, - required this.glossaryRepository, - required this.profileRepository, - required this.dietRepository}) - : super(const DiaryState.loading()) { + DiaryCubit({ + required this.chatRepository, + required this.diaryRepository, + required this.glossaryRepository, + required this.profileRepository, + required this.dietRepository, + required this.settingsRepository, + }) : super(const DiaryState.loading()) { today = DateTime.now(); selectedDay = today; diaryRepository.forceFetchStream.listen((_) { @@ -160,11 +163,16 @@ class DiaryCubit extends Cubit { } try { final DiaryDayModel diaryDay; + bool isMoodEnabled = false; + bool isWakeUpAndRetireTimeEnabled = false; if (diaryId != null) { diaryDay = await diaryRepository.getDiaryDay(diaryId); } else { final stringDate = selectedDay.format('yyyy-MM-dd'); diaryDay = await diaryRepository.getDiaryByDate(stringDate); + final profileSettings = await settingsRepository.getProfileSettings(); + isMoodEnabled = profileSettings.isMoodFillable; + isWakeUpAndRetireTimeEnabled = profileSettings.isBedtimeFillable; } ConsultantReviewModel? consultantReview; int? planDuration; @@ -197,6 +205,8 @@ class DiaryCubit extends Cubit { chosenPlanCurrentDay: currentPlanDay, chosenPlanDuration: planDuration, chosenPlanName: planName, + isMoodEnabled: isMoodEnabled, + isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled ), ); } catch (e, st) { diff --git a/lib/features/diary/presentation/bloc/diary_state.dart b/lib/features/diary/presentation/bloc/diary_state.dart index 828831d3..d5e8e39d 100644 --- a/lib/features/diary/presentation/bloc/diary_state.dart +++ b/lib/features/diary/presentation/bloc/diary_state.dart @@ -27,6 +27,8 @@ abstract class DiaryState with _$DiaryState { @Default(false) bool isConsultantView, @Default(false) bool isDiaryFinished, @Default(false) bool isDiaryCommented, + @Default(false) bool isWakeUpAndRetireEnabled, + @Default(false) bool isMoodEnabled, }) = DiaryStateLoaded; const factory DiaryState.loadedTomorrowWithDiet({ diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 2f80c22e..fb1987ef 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -80,13 +80,17 @@ class DiaryScreenContent extends StatelessWidget { return Scaffold( backgroundColor: Colors.grey.shade200, - appBar: AppBar( - title: Text( - state is DiaryStateLoaded ? DateTime.parse(state.diary.date).toLocal().format('d MMMM') : '', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - centerTitle: true, - ), + appBar: diaryId == null + ? AppBar( + toolbarHeight: 0, + ) + : AppBar( + title: Text( + state is DiaryStateLoaded ? DateTime.parse(state.diary.date).toLocal().format('d MMMM') : '', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + centerTitle: true, + ), body: SingleChildScrollView( padding: const EdgeInsets.only(bottom: 16, top: 8), physics: const BouncingScrollPhysics(), @@ -101,13 +105,13 @@ class DiaryScreenContent extends StatelessWidget { ], if (state is DiaryStateLoaded) WakeupAndRetireWidget( - showRetire: state.diary.typeOfDiary.hasChosenPlan, + isEnabled: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isWakeUpAndRetireEnabled, disable: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented, ), const SizedBox(height: 12), if (state is DiaryStateLoaded) MealWidget( - showMood: state.diary.typeOfDiary.hasChosenPlan, + showMood: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isMoodEnabled, showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented, ), if (state is DiaryStateLoadedTomorrowWithDiet) diff --git a/lib/features/diary/presentation/widgets/calories_chart_widget.dart b/lib/features/diary/presentation/widgets/calories_chart_widget.dart index 6cdc6e86..817508af 100644 --- a/lib/features/diary/presentation/widgets/calories_chart_widget.dart +++ b/lib/features/diary/presentation/widgets/calories_chart_widget.dart @@ -30,7 +30,7 @@ class CaloriesChartWidget extends StatelessWidget { context: context, builder: (context) => const AlertDialog( content: Text( - 'График отображает сколько калорий вам нужно ежедневно для похуденияи поддержания веса, согласно формуле Харриса-Бенедикта.\nИзменить коэффициент физической нагрузки можно в настройках.', + 'График отображает сколько калорий Вам нужно ежедневно для поддержания веса, согласно формуле Харриса-Бенедикта. Изменить коэффициент физической нагрузки можно в настройках', style: TextStyle( fontWeight: FontWeight.w500, color: Color(0xFF323F4B), diff --git a/lib/features/diary/presentation/widgets/meal_widget.dart b/lib/features/diary/presentation/widgets/meal_widget.dart index b5bd196a..ce71ef66 100644 --- a/lib/features/diary/presentation/widgets/meal_widget.dart +++ b/lib/features/diary/presentation/widgets/meal_widget.dart @@ -59,7 +59,6 @@ class _MealWidgetState extends State { ), const SizedBox(height: 12), const TimeOfMealPickerWidget(), - const ChosenDietWidget(), const ChosenPlanWidget(), const ProductsListWidget(), diff --git a/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart b/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart index 8c9f6028..1dbe9925 100644 --- a/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart +++ b/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart @@ -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, required this.showRetire, this.disable = false}); - final bool showRetire; + const WakeupAndRetireWidget({super.key, required this.isEnabled, this.disable = false}); + final bool isEnabled; final bool disable; @override State createState() => _WakeupAndRetireWidgetState(); @@ -94,6 +94,9 @@ class _WakeupAndRetireWidgetState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { + if (!widget.isEnabled) { + return const SizedBox.shrink(); + } if (state is DiaryStateLoaded) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), @@ -157,67 +160,65 @@ class _WakeupAndRetireWidgetState extends State { ), ), ), - 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, + 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: widget.disable ? null : () => 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, - height: 1, - ), - ), - ], - ), - const SizedBox(height: 8), - InkWell( - onTap: widget.disable ? null : () => 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, - ), ), ), ), ), - ], - ), + ), + ], ), ), ), - ] + ), ], ), ); From 2c3cc199c06b3cdce28b816e7cdae3f6eb3edfdf Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 9 Jun 2025 19:53:26 +0700 Subject: [PATCH 4/6] fix: change baseUrl --- lib/core/const.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/core/const.dart b/lib/core/const.dart index 51e7feff..31c47e9c 100644 --- a/lib/core/const.dart +++ b/lib/core/const.dart @@ -1,8 +1,5 @@ -void qwe() { - print('change to prod url'); -} -// String baseUrl = 'https://eatvio.ru'; -String baseUrl = 'https://dev2.eatvio.com'; +String baseUrl = 'https://eatvio.ru'; +// String baseUrl = 'https://dev2.eatvio.com'; const List durationValues = [7, 14, 21]; From 926f0de450636fce9e27d52f2dba98f943328474 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Tue, 10 Jun 2025 19:57:20 +0700 Subject: [PATCH 5/6] feat(userPurchases, consultantFinances): added status to models and layout for status --- .../models/consultant_finance_model.dart | 7 ++--- .../models/user_purchases_model.dart | 6 ++-- .../consultant_finances_screen.dart | 14 +++++++-- .../consultations_archive_screen.dart | 12 +++++++- .../presentation/user_purchases_screen.dart | 21 ++++++++++---- .../presentation/widgets/status_widget.dart | 29 ++++++++++++------- .../menu/presentation/pages/menu_screen.dart | 1 - 7 files changed, 61 insertions(+), 29 deletions(-) diff --git a/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart b/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart index 41161f22..9e8cc751 100644 --- a/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart +++ b/lib/features/finances_and_consultations_archive/models/consultant_finance_model.dart @@ -13,6 +13,7 @@ abstract class ConsultantFinanceModel with _$ConsultantFinanceModel { @JsonKey(name: 'clientName') required String clientName, @JsonKey(name: 'duration') required int duration, @JsonKey(name: 'payment_price', fromJson: _parsePayment) required String paymentPrice, + required PaymentStatus? paymentStatus, }) = _ConsultantFinanceModel; factory ConsultantFinanceModel.fromJson(Map json) => _$ConsultantFinanceModelFromJson(json); @@ -28,9 +29,5 @@ enum PaymentStatus { @JsonValue('REJECTED') rejected, @JsonValue('CHECKED') - checked, - @JsonValue('CREDIT_CHECKING') - creditChecking, - @JsonValue('COMPLETING') - completing; + checked } diff --git a/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart b/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart index deaf42dc..10c89545 100644 --- a/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart +++ b/lib/features/finances_and_consultations_archive/models/user_purchases_model.dart @@ -1,4 +1,5 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; +import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'user_purchases_model.freezed.dart'; @@ -8,12 +9,13 @@ part 'user_purchases_model.g.dart'; abstract class UserPurchasesModel with _$UserPurchasesModel { factory UserPurchasesModel({ @JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate, - @JsonKey(name: 'bank_id') required String? bankId, + @JsonKey(name: 'bank_id') required String bankId, @JsonKey(name: 'title') required String title, @JsonKey(name: 'consultantName') required String consultantName, @JsonKey(name: 'duration') required int duration, @JsonKey(name: 'price') required int paymentPrice, + @JsonKey(name: 'chosen_plan_last_state') required ChosenPlanStateEnum chosenPlanState, }) = _UserPurchasesModel; factory UserPurchasesModel.fromJson(Map json) => _$UserPurchasesModelFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart b/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart index 9d1769c4..7e731474 100644 --- a/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart +++ b/lib/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart @@ -7,7 +7,9 @@ import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_widget.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'; @@ -102,6 +104,12 @@ class _ConsultantFinancesScreenState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { + final Status modelStatus = switch (state.financesModels[index].paymentStatus) { + null => Status.inProgress, + PaymentStatus.completed => Status.success, + PaymentStatus.rejected => Status.canceled, + PaymentStatus.checked => Status.success, + }; return Material( color: const Color(0xFFF6F6F6), borderRadius: BorderRadius.circular(20), @@ -181,9 +189,9 @@ class _ConsultantFinancesScreenState extends State { fontWeight: FontWeight.w600, ), ), - // StatusWidget( - // status: state.consultations[index].chosenPlanState, - // ), + StatusWidget( + status: modelStatus, + ), ], ), ], diff --git a/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart b/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart index 9bdecd59..ec2d4900 100644 --- a/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart +++ b/lib/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart @@ -9,6 +9,7 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/arc import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_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'; @@ -165,6 +166,15 @@ class _ConsultationsArchiveScreenState extends State shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { + final Status consultationStatus; + if (state.consultations[index].chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) { + consultationStatus = Status.canceled; + } else if (state.consultations[index].chosenPlanState == ChosenPlanStateEnum.stateChargeWasCollected) { + consultationStatus = Status.success; + } else { + consultationStatus = Status.inProgress; + } + return Material( color: const Color(0xFFF6F6F6), borderRadius: BorderRadius.circular(20), @@ -246,7 +256,7 @@ class _ConsultationsArchiveScreenState extends State ), ), StatusWidget( - status: state.consultations[index].chosenPlanState, + status: consultationStatus, ), ], ), diff --git a/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart b/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart index e1f9f036..6f137211 100644 --- a/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart +++ b/lib/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart @@ -8,6 +8,8 @@ import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; +import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/widgets/status_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'; @@ -84,6 +86,14 @@ class _UserPurchasesScreen extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { + final Status purchaseStatus; + if (state.purchasesModels[index].chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) { + purchaseStatus = Status.canceled; + } else if (state.purchasesModels[index].chosenPlanState == ChosenPlanStateEnum.stateChargeWasCollected) { + purchaseStatus = Status.success; + } else { + purchaseStatus = Status.inProgress; + } return Material( color: const Color(0xFFF6F6F6), borderRadius: BorderRadius.circular(20), @@ -96,10 +106,9 @@ class _UserPurchasesScreen extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Builder(builder: (context) { - // final idLen = state.purchasesModels[index].bankId.length; - final bankId = state.purchasesModels[index].bankId; + final idLen = state.purchasesModels[index].bankId.length; return Text( - '$bankId', + '${state.purchasesModels[index].bankId.substring(0, 4)}...${state.purchasesModels[index].bankId.substring(idLen - 4, idLen)} ', style: TextStyles.textSMedium.copyWith(color: appTheme.black), ); }), @@ -164,9 +173,9 @@ class _UserPurchasesScreen extends State { fontWeight: FontWeight.w600, ), ), - // StatusWidget( - // status: state.consultations[index].chosenPlanState, - // ), + StatusWidget( + status: purchaseStatus, + ), ], ), ], diff --git a/lib/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart b/lib/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart index d53c254a..fab1fc21 100644 --- a/lib/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart +++ b/lib/features/finances_and_consultations_archive/presentation/widgets/status_widget.dart @@ -1,15 +1,15 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; import 'package:flutter/material.dart'; class StatusWidget extends StatelessWidget { const StatusWidget({super.key, required this.status}); - final ChosenPlanStateEnum status; + final Status status; @override Widget build(BuildContext context) { - if (status == ChosenPlanStateEnum.stateCanceledByTheInitiator) { - return Material( + return switch (status){ + + Status.canceled => Material( color: appTheme.lightRed, borderRadius: BorderRadius.circular(8), child: Padding( @@ -24,9 +24,9 @@ class StatusWidget extends StatelessWidget { ), ), ), - ); - } else if (status == ChosenPlanStateEnum.stateChargeWasCollected) { - return Material( + ), + + Status.success => Material( color: const Color(0xFF0BB450).withAlpha(11), borderRadius: BorderRadius.circular(8), child: const Padding( @@ -41,9 +41,9 @@ class StatusWidget extends StatelessWidget { ), ), ), - ); - } - return Material( + ), + + Status.inProgress => Material( color: appTheme.transparentBlue, borderRadius: BorderRadius.circular(8), child: Padding( @@ -58,6 +58,13 @@ class StatusWidget extends StatelessWidget { ), ), ), - ); + ), + }; } } + +enum Status { + canceled, + success, + inProgress +} \ No newline at end of file diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index 8b3506ed..f657c6a7 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -44,7 +44,6 @@ class MenuScreen extends StatelessWidget { onTap: () { if (profileModel.roles.contains('consultant')) { GoRouter.of(context).push(ConsultantFinancesScreen.path); - GoRouter.of(context).push(UserPurchasesScreen.path); return; } GoRouter.of(context).push(UserPurchasesScreen.path); From f163608a90cdc5971e59b26f0989041fa292f93b Mon Sep 17 00:00:00 2001 From: Username1548 Date: Wed, 11 Jun 2025 20:13:26 +0700 Subject: [PATCH 6/6] feat(permissions): added galery and camera permisshion checker --- ios/Podfile | 11 ++ .../permission_handler.dart | 23 ++++ lib/core/widgets/show_error_dialog.dart | 12 +- .../pages/blog_post_create_screen.dart | 97 +++++++++------ .../widgets/diet_image_picker_widget.dart | 38 ++++-- .../widgets/plan_image_picker_widget.dart | 38 ++++-- .../presentation/widgets/image_widget.dart | 114 ++++++++++------- .../widgets/step_image_widget.dart | 113 ++++++++++------- .../widgets/photo_gallery_widget.dart | 117 +++++++++++------- .../presentation/pages/profile_screen.dart | 112 ++++++++++------- lib/generated/intl/messages_en.dart | 13 ++ lib/generated/l10n.dart | 50 ++++++++ lib/l10n/intl_en.arb | 7 +- pubspec.yaml | 1 + 14 files changed, 502 insertions(+), 244 deletions(-) create mode 100644 lib/core/permission_handler/permission_handler.dart diff --git a/ios/Podfile b/ios/Podfile index 27c089c7..53a8d390 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -40,6 +40,17 @@ post_install do |installer| target.build_configurations.each do |config| config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + ## dart: PermissionGroup.camera + 'PERMISSION_CAMERA=1', + ## dart: PermissionGroup.photos + 'PERMISSION_PHOTOS=1', + 'PERMISSION_NOTIFICATIONS=1', + ## dart: PermissionGroup.mediaLibrary + 'PERMISSION_MEDIA_LIBRARY=1', + ] + end end end diff --git a/lib/core/permission_handler/permission_handler.dart b/lib/core/permission_handler/permission_handler.dart new file mode 100644 index 00000000..a311e84d --- /dev/null +++ b/lib/core/permission_handler/permission_handler.dart @@ -0,0 +1,23 @@ +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class PermissionHandler { + static Future checkPermission({ + required Permission permission, + required String title, + required String description, + required Future Function() onGranted, + required BuildContext context, + }) async { + final permissionState = await permission.request(); + if (permissionState.isGranted) { + await onGranted(); + return; + } + if (context.mounted) { + showErrorDialog(context, title: title, description: description, buttonTitle: S.of(context).allowAccess, needToOpenSettings: true); + } + } +} diff --git a/lib/core/widgets/show_error_dialog.dart b/lib/core/widgets/show_error_dialog.dart index 9685ad14..8ae4666e 100644 --- a/lib/core/widgets/show_error_dialog.dart +++ b/lib/core/widgets/show_error_dialog.dart @@ -4,8 +4,9 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:permission_handler/permission_handler.dart'; -Future showErrorDialog(BuildContext context, {String? title, String? description}) async { +Future showErrorDialog(BuildContext context, {String? title, String? description, String? buttonTitle, bool? needToOpenSettings}) async { return await showDialog( context: context, barrierColor: Colors.transparent, @@ -43,8 +44,13 @@ Future showErrorDialog(BuildContext context, {String? title, String? descr Align( alignment: Alignment.centerRight, child: TextButton( - onPressed: () => AppRouter.navigatorKey.currentContext?.pop(), - child: Text(s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + onPressed: () { + AppRouter.navigatorKey.currentContext?.pop(); + if (needToOpenSettings ?? false) { + openAppSettings(); + } + }, + child: Text(buttonTitle ?? s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), ), ), const SizedBox(height: 12), diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index f7b57ddd..d1682338 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:io'; +import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/core/theme/app_decoration.dart'; import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart'; @@ -17,6 +19,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class BlogPostCreateScreen extends StatefulWidget { final FeedBlogPostModel? feedBlogPostModel; @@ -80,47 +83,65 @@ class _BlogPostCreateScreenState extends State { _showImagePicker() { showCupertinoModalPopup( context: context, - builder: (context) => CupertinoActionSheet( - actions: [ - CupertinoActionSheetAction( - onPressed: () async { - final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.camera); - setState(() {}); - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: const Row( - children: [ - Icon(Icons.photo_camera), - SizedBox(width: 8), - Text('Фото'), - ], + builder: (context) { + final s = S.of(context); + return CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + final picker = ImagePicker(); + image = await picker.pickImage(source: ImageSource.camera); + setState(() {}); + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), ), - ), - CupertinoActionSheetAction( - onPressed: () async { - final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.gallery); - setState(() {}); - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: const Row( - children: [ - Icon(Icons.photo), - SizedBox(width: 8), - Text('Галерея'), - ], + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + final picker = ImagePicker(); + image = await picker.pickImage(source: ImageSource.gallery); + setState(() {}); + if (!context.mounted) return; + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), + ], + ), ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), ), - ], - cancelButton: CupertinoActionSheetAction( - onPressed: () => Navigator.of(context).pop(), - isDestructiveAction: true, - child: const Text('Отмена'), - ), - ), + ); + }, ); } diff --git a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart index c6250bb4..192df5d3 100644 --- a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:dotted_border/dotted_border.dart'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.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/cubit/create_diet_cubit.dart'; @@ -11,7 +13,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class DietImagePickerWidget extends StatefulWidget { const DietImagePickerWidget({super.key}); @@ -28,11 +32,18 @@ class _DietImagePickerWidgetState extends State { actions: [ CupertinoActionSheetAction( onPressed: () async { - final picker = ImagePicker(); - final image = await picker.pickImage(source: ImageSource.camera); - cubit.setImage(image); - if (!context.mounted) return; - Navigator.of(context).pop(); + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.camera); + cubit.setImage(image); + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); }, child: Row( children: [ @@ -44,11 +55,18 @@ class _DietImagePickerWidgetState extends State { ), CupertinoActionSheetAction( onPressed: () async { - final picker = ImagePicker(); - final image = await picker.pickImage(source: ImageSource.gallery); - cubit.setImage(image); - if (!context.mounted) return; - Navigator.of(context).pop(); + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.gallery); + cubit.setImage(image); + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); }, child: Row( children: [ diff --git a/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart b/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart index d5041035..67af4a61 100644 --- a/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart +++ b/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:dotted_border/dotted_border.dart'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.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'; @@ -11,7 +13,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class PlanImagePickerWidget extends StatefulWidget { const PlanImagePickerWidget({super.key}); @@ -28,11 +32,18 @@ class _PlanImagePickerWidgetState extends State { actions: [ CupertinoActionSheetAction( onPressed: () async { - final picker = ImagePicker(); - final image = await picker.pickImage(source: ImageSource.camera); - cubit.setImage(image); - if (!context.mounted) return; - Navigator.of(context).pop(); + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.camera); + cubit.setImage(image); + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); }, child: Row( children: [ @@ -44,11 +55,18 @@ class _PlanImagePickerWidgetState extends State { ), CupertinoActionSheetAction( onPressed: () async { - final picker = ImagePicker(); - final image = await picker.pickImage(source: ImageSource.gallery); - cubit.setImage(image); - if (!context.mounted) return; - Navigator.of(context).pop(); + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.gallery); + cubit.setImage(image); + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); }, child: Row( children: [ diff --git a/lib/features/create_recipe/presentation/widgets/image_widget.dart b/lib/features/create_recipe/presentation/widgets/image_widget.dart index 534499e4..eb444b8e 100644 --- a/lib/features/create_recipe/presentation/widgets/image_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/image_widget.dart @@ -1,11 +1,16 @@ import 'dart:io'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class ImageWidget extends StatefulWidget { const ImageWidget({super.key, required this.image}); @@ -67,55 +72,72 @@ class _ImageWidgetState extends State { _showImagePicker() { showCupertinoModalPopup( context: context, - builder: (context) => CupertinoActionSheet( - actions: [ - CupertinoActionSheetAction( - onPressed: () async { - final picker = ImagePicker(); - final image = await picker.pickImage(source: ImageSource.camera); - setState(() {}); - if (image != null) { - createRecipeCubit.replaceImage(File(image.path)); - createRecipeCubit.validateImage(); - } - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: const Row( - children: [ - Icon(Icons.photo_camera), - SizedBox(width: 8), - Text('Фото'), - ], + builder: (context) { + final s = S.of(context); + return CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.camera); + setState(() {}); + if (image != null) { + createRecipeCubit.replaceImage(File(image.path)); + createRecipeCubit.validateImage(); + } + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), ), - ), - CupertinoActionSheetAction( - onPressed: () async { - final picker = ImagePicker(); - final image = await picker.pickImage(source: ImageSource.gallery); - setState(() {}); - if (image != null) { - createRecipeCubit.replaceImage(File(image.path)); - createRecipeCubit.validateImage(); - } - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: const Row( - children: [ - Icon(Icons.photo), - SizedBox(width: 8), - Text('Галерея'), - ], + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.gallery); + setState(() {}); + if (image != null) { + createRecipeCubit.replaceImage(File(image.path)); + createRecipeCubit.validateImage(); + } + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), + ], + ), ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), ), - ], - cancelButton: CupertinoActionSheetAction( - onPressed: () => Navigator.of(context).pop(), - isDestructiveAction: true, - child: const Text('Отмена'), - ), - ), + ); + }, ); } } diff --git a/lib/features/create_recipe/presentation/widgets/step_image_widget.dart b/lib/features/create_recipe/presentation/widgets/step_image_widget.dart index 1189f0f8..d00d08c7 100644 --- a/lib/features/create_recipe/presentation/widgets/step_image_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/step_image_widget.dart @@ -1,10 +1,15 @@ import 'dart:io'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class StepImageWidget extends StatefulWidget { final CreateRecipeCubit createRecipeCubit; @@ -12,7 +17,8 @@ class StepImageWidget extends StatefulWidget { const StepImageWidget({ super.key, - required this.index, required this.createRecipeCubit, + required this.index, + required this.createRecipeCubit, }); @override @@ -79,53 +85,70 @@ class _StepImageWidgetState extends State { _showImagePicker() { showCupertinoModalPopup( context: context, - builder: (context) => CupertinoActionSheet( - actions: [ - CupertinoActionSheetAction( - onPressed: () async { - final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.camera); - setState(() {}); - if (image != null) { - widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index); - } - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: const Row( - children: [ - Icon(Icons.photo_camera), - SizedBox(width: 8), - Text('Фото'), - ], + builder: (context) { + final s = S.of(context); + return CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + final picker = ImagePicker(); + image = await picker.pickImage(source: ImageSource.camera); + setState(() {}); + if (image != null) { + widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index); + } + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), ), - ), - CupertinoActionSheetAction( - onPressed: () async { - final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.gallery); - setState(() {}); - if (image != null) { - widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index); - } - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: const Row( - children: [ - Icon(Icons.photo), - SizedBox(width: 8), - Text('Галерея'), - ], + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + final picker = ImagePicker(); + image = await picker.pickImage(source: ImageSource.gallery); + setState(() {}); + if (image != null) { + widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index); + } + AppRouter.navigatorKey.currentContext?.pop(); + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), + ], + ), ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), ), - ], - cancelButton: CupertinoActionSheetAction( - onPressed: () => Navigator.of(context).pop(), - isDestructiveAction: true, - child: const Text('Отмена'), - ), - ), + ); + }, ); } } diff --git a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart index 80c7e58a..7a89d466 100644 --- a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart +++ b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart @@ -1,16 +1,21 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.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/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class PhotoGalleryWidget extends StatefulWidget { const PhotoGalleryWidget({super.key}); @@ -32,55 +37,75 @@ class _PhotoGalleryWidgetState extends State { showImagePicker(DiaryDayModel diaryDay) { showCupertinoModalPopup( context: context, - builder: (context) => CupertinoActionSheet( - actions: [ - CupertinoActionSheetAction( - onPressed: () async { - Navigator.of(context).pop(); - final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.camera); - if (image != null) { - diaryCubit.uploadPhotos( - files: [File(image.path)], - diaryId: diaryDay.id, + builder: (context) { + final s = S.of(context); + return CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + AppRouter.navigatorKey.currentContext?.pop(); + + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.camera); + if (image != null) { + diaryCubit.uploadPhotos( + files: [File(image.path)], + diaryId: diaryDay.id, + ); + } + }, + context: context, ); - } - }, - child: const Row( - children: [ - Icon(Icons.photo_camera), - SizedBox(width: 8), - Text('Фото'), - ], + }, + child: const Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), ), - ), - CupertinoActionSheetAction( - onPressed: () async { - Navigator.of(context).pop(); - final picker = ImagePicker(); - List? images = await picker.pickMultiImage(); - if (images.isNotEmpty) { - diaryCubit.uploadPhotos( - files: images.map((f) => File(f.path)).toList(), - diaryId: diaryDay.id, + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + AppRouter.navigatorKey.currentContext?.pop(); + final picker = ImagePicker(); + List? images = await picker.pickMultiImage(); + if (images.isNotEmpty) { + diaryCubit.uploadPhotos( + files: images.map((f) => File(f.path)).toList(), + diaryId: diaryDay.id, + ); + } + }, + context: context, ); - } - }, - child: const Row( - children: [ - Icon(Icons.photo), - SizedBox(width: 8), - Text('Галерея'), - ], + }, + child: const Row( + children: [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), + ], + ), ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), ), - ], - cancelButton: CupertinoActionSheetAction( - onPressed: () => Navigator.of(context).pop(), - isDestructiveAction: true, - child: const Text('Отмена'), - ), - ), + ); + }, ); } @@ -92,7 +117,9 @@ class _PhotoGalleryWidgetState extends State { int mealIndex = state.selectedMeal - 1; if (state.diary.meals[mealIndex].photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) { return GestureDetector( - onTap: () => showImagePicker(state.diary), + onTap: () async { + showImagePicker(state.diary); + }, child: SvgPicture.asset( width: MediaQuery.of(context).size.width - 32, 'assets/no_one_image_was_added.svg', diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index ef9aac1a..6d99d92b 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -2,8 +2,10 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dismissible_page/dismissible_page.dart'; +import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; @@ -26,6 +28,7 @@ import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_po import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -34,6 +37,7 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -600,9 +604,7 @@ class _ProfileScreenState extends State { _educationBlock(String title, String date) { final parsedDate = DateTime.tryParse(date); - final formattedDate = parsedDate != null - ? DateFormat('d MMMM y', 'ru_RU').format(parsedDate) - : date; + final formattedDate = parsedDate != null ? DateFormat('d MMMM y', 'ru_RU').format(parsedDate) : date; return SizedBox( width: double.infinity, @@ -640,7 +642,6 @@ class _ProfileScreenState extends State { ); } - _socialLinks(ProfileStateLoaded state) { if (!state.profile.roles.contains('consultant')) { return const SizedBox.shrink(); @@ -993,51 +994,70 @@ class _ProfileScreenState extends State { if (value == 'replace') { showCupertinoModalPopup( context: context, - builder: (context) => CupertinoActionSheet( - actions: [ - CupertinoActionSheetAction( - onPressed: () async { - Navigator.of(context).pop(); - final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.camera); - if (image != null) { - await profileCubit.uploadPhoto(File(image.path)); - await sl().getMyProfile(); - } - }, - child: const Row( - children: [ - Icon(Icons.photo_camera), - SizedBox(width: 8), - Text('Фото'), - ], + builder: (context) { + final s = S.of(context); + return CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.camera, + title: s.allowAccessToCamera, + description: s.allowAccessToCameraDescription, + onGranted: () async { + AppRouter.navigatorKey.currentContext?.pop(); + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.camera); + if (image != null) { + await profileCubit.uploadPhoto(File(image.path)); + await sl().getMyProfile(); + } + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), ), - ), - CupertinoActionSheetAction( - onPressed: () async { - Navigator.of(context).pop(); - final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.gallery); - if (image != null) { - await profileCubit.uploadPhoto(File(image.path)); - await sl().getMyProfile(); - } - }, - child: const Row( - children: [ - Icon(Icons.photo), - SizedBox(width: 8), - Text('Галерея'), - ], + CupertinoActionSheetAction( + onPressed: () async { + PermissionHandler.checkPermission( + permission: Permission.photos, + title: s.allowAcessToGalery, + description: s.allowAcessToGaleryDescription, + onGranted: () async { + AppRouter.navigatorKey.currentContext?.pop(); + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.gallery); + if (image != null) { + await profileCubit.uploadPhoto(File(image.path)); + await sl().getMyProfile(); + } + }, + context: context, + ); + }, + child: const Row( + children: [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), + ], + ), ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), ), - ], - cancelButton: CupertinoActionSheetAction( - onPressed: () => Navigator.of(context).pop(), - isDestructiveAction: true, - child: const Text('Отмена'), - ), - ), + ); + }, ); } if (value == 'delete') { diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 611eb301..e318c962 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -73,6 +73,19 @@ class MessageLookup extends MessageLookupByLibrary { "allergies_presence": MessageLookupByLibrary.simpleMessage( "Наличие аллергии", ), + "allowAccess": MessageLookupByLibrary.simpleMessage("Разрешить доступ"), + "allowAccessToCamera": MessageLookupByLibrary.simpleMessage( + "Разрешите доступ к камере", + ), + "allowAccessToCameraDescription": MessageLookupByLibrary.simpleMessage( + "Предоставление доступа позволит вам делать фотографии и добавлять к записям", + ), + "allowAcessToGalery": MessageLookupByLibrary.simpleMessage( + "Разрешите доступ к фото", + ), + "allowAcessToGaleryDescription": MessageLookupByLibrary.simpleMessage( + "Предоставление доступа позволит вам добавлять фото к записям", + ), "another_form_of_comment": MessageLookupByLibrary.simpleMessage( "комментария", ), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 0bfd0ea0..9f338062 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -3344,6 +3344,56 @@ class S { args: [], ); } + + /// `Разрешите доступ к фото` + String get allowAcessToGalery { + return Intl.message( + 'Разрешите доступ к фото', + name: 'allowAcessToGalery', + desc: '', + args: [], + ); + } + + /// `Предоставление доступа позволит вам добавлять фото к записям` + String get allowAcessToGaleryDescription { + return Intl.message( + 'Предоставление доступа позволит вам добавлять фото к записям', + name: 'allowAcessToGaleryDescription', + desc: '', + args: [], + ); + } + + /// `Разрешите доступ к камере` + String get allowAccessToCamera { + return Intl.message( + 'Разрешите доступ к камере', + name: 'allowAccessToCamera', + desc: '', + args: [], + ); + } + + /// `Предоставление доступа позволит вам делать фотографии и добавлять к записям` + String get allowAccessToCameraDescription { + return Intl.message( + 'Предоставление доступа позволит вам делать фотографии и добавлять к записям', + name: 'allowAccessToCameraDescription', + desc: '', + args: [], + ); + } + + /// `Разрешить доступ` + String get allowAccess { + return Intl.message( + 'Разрешить доступ', + name: 'allowAccess', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1145f9d1..96ddc4be 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -397,6 +397,11 @@ "planStarted": "План «{planName}» начался", "userCanceldThePlanSubscription": "Пользователь {name} отменил подписку на план «{planName}»", "userSubscribedToPlan": "Пользователь {name} подписался на план «{planName}»", - "markAsRead": "Отметить всё прочитанным" + "markAsRead": "Отметить всё прочитанным", + "allowAcessToGalery": "Разрешите доступ к фото", + "allowAcessToGaleryDescription": "Предоставление доступа позволит вам добавлять фото к записям", + "allowAccessToCamera": "Разрешите доступ к камере", + "allowAccessToCameraDescription": "Предоставление доступа позволит вам делать фотографии и добавлять к записям", + "allowAccess": "Разрешить доступ" } diff --git a/pubspec.yaml b/pubspec.yaml index 97712189..086abd4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,7 @@ dependencies: shared_preferences: ^2.5.3 version: ^3.0.2 package_info_plus: ^8.3.0 + permission_handler: ^12.0.0+1 dev_dependencies: build_runner: ^2.4.15