import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../core/app_colors.dart'; import '../../data/network/api_service.dart'; import '../../di/service_locator.dart'; import '../../domain/entities/payment_card.dart'; import '../../domain/usecase/add_payment_card_usecase.dart'; import '../../domain/usecase/get_payment_cards_usecase.dart'; import '../../domain/usecase/get_profile_usecase.dart'; import '../../domain/usecase/set_main_payment_card_usecase.dart'; import '../components/custom_app_bar.dart'; import '../components/full_screen_image_viewer.dart'; import '../components/gradient_button.dart'; import '../components/payment_option.dart'; import '../components/sheet/payment_method_sheet.dart'; import '../event/fine_payment_event.dart'; import '../event/payment_method_sheet_event.dart'; import '../state/fine_payment_state.dart'; import '../viewmodel/fine_payment_bloc.dart'; import '../viewmodel/payment_method_sheet_bloc.dart'; class FinePaymentScreen extends StatelessWidget { final int fineId; const FinePaymentScreen({ super.key, required this.fineId, }); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => FinePaymentBloc( getFineDetailsUsecase: getIt(), payFineUsecase: getIt(), )..add(FetchFineDetails(fineId)), child: _FinePaymentScreenContent(fineId: fineId), ); } } class _FinePaymentScreenContent extends StatelessWidget { final int fineId; const _FinePaymentScreenContent({required this.fineId}); String _resolveImageUrl(String path) { if (path.startsWith('http://') || path.startsWith('https://')) return path; return 'https://sharing-api.sparkit.by/$path'; } void _openFullScreenImage(BuildContext context, List photos, int initialIndex) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => FullScreenImageViewer( photoUrls: photos, initialIndex: initialIndex, ), ), ); } @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg), child: SafeArea( child: Stack( children: [ Positioned( bottom: 60, left: 0, right: 0, child: Image.asset( 'assets/wave.png', fit: BoxFit.fitWidth, color: Colors.white.withOpacity(0.1), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), const Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: CustomAppBar(title: 'Оплата штрафа'), ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24), child: BlocConsumer( listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { if (state.status == FinePaymentStatus.success && state.paymentCompleted) { context.go('/home/fine-payment/$fineId/result/success'); } else if (state.status == FinePaymentStatus.failure) { if (state.fine != null) { // штраф уже был загружен — значит ошибка произошла при попытке оплаты context.go('/home/fine-payment/$fineId/result/failed'); } else { // штраф не удалось загрузить — остаёмся на месте, показываем снекбар ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( state.errorMessage ?? 'Произошла ошибка при оплате штрафа', style: const TextStyle(color: Colors.white), ), backgroundColor: Colors.redAccent.withOpacity(0.9), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), margin: const EdgeInsets.all(20), duration: const Duration(seconds: 3), ), ); } } }, builder: (context, state) { final fine = state.fine; if (state.status == FinePaymentStatus.loading && fine == null) { return const SizedBox( height: 400, child: Center( child: CircularProgressIndicator(color: Colors.white), ), ); } if (fine == null) return const SizedBox.shrink(); final bool isPaid = fine.isPaid; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), if (fine.photoUrls.isNotEmpty) SizedBox( height: 110, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: fine.photoUrls.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) { final photo = fine.photoUrls[index]; final imageUrl = _resolveImageUrl(photo); return GestureDetector( onTap: () => _openFullScreenImage( context, fine.photoUrls, index, ), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( imageUrl, width: 110, height: 110, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return SizedBox( width: 110, height: 110, child: Center( child: SizedBox( width: 28, height: 28, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ), ), ); }, errorBuilder: (context, error, stackTrace) { return const Icon(Icons.error, color: Colors.white, size: 48); }, ), ), ); }, ), ), const SizedBox(height: 24), _buildInfoLabel('Номер заказа:'), _buildValueRow( 'qr_icon.png', '${fine.orderId}', ), const SizedBox(height: 20), _buildInfoLabel('Дата и время:'), Text( _formatDateTime(fine.createdAtDateTime), style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 20), _buildInfoLabel('Описание:'), Text( fine.description, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 28), Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.07), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: Column( children: [ Text( 'Сумма штрафа:', style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 16, ), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/icons/money_icon.png', width: 36, height: 36, ), const SizedBox(width: 10), Text( fine.amountPrint, style: const TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold, ), ), ], ), ], ), ), const SizedBox(height: 28), if (!isPaid) (state.useBalance || state.selectedCard != null) ? PaymentOption( title: state.useBalance ? 'Баланс' : "", subtitle: state.useBalance ? '${state.userBalance.toStringAsFixed(2)} BYN' : '${state.selectedCard!.cardLastNumber}', isSelected: true, onTap: () async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (innerContext) => BlocProvider( create: (context) => PaymentMethodSheetBloc( getIt(), getIt(), getIt(), getIt(), )..add(PaymentMethodSheetStarted()), child: PaymentMethodSheet( initialSelectedCard: state.useBalance ? null : state.selectedCard, ), ), ); if (result != null && context.mounted) { if (result is PaymentCard) { context.read().add(FinePaymentCardChanged(result)); } else if (result == 'balance') { context.read().add(SelectFineBalancePressed()); } } }, ) : Container( height: 56, decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), border: Border.all( color: Colors.white.withOpacity(0.4), width: 1, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (innerContext) => BlocProvider( create: (context) => PaymentMethodSheetBloc( getIt(), getIt(), getIt(), getIt(), )..add(PaymentMethodSheetStarted()), child: const PaymentMethodSheet(), ), ); if (result != null && context.mounted) { if (result is PaymentCard) { context.read().add(FinePaymentCardChanged(result)); } else if (result == 'balance') { context.read().add(SelectFineBalancePressed()); } } }, borderRadius: BorderRadius.circular(24), child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Способ оплаты', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 8), Icon( Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.6), ), Icon( Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.4), ), Icon( Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.2), ), ], ), ), ), ), ), const SizedBox(height: 20), ], ); }, ), ), ), Padding( padding: const EdgeInsets.all(24.0), child: BlocBuilder( builder: (context, state) { final fine = state.fine; if (fine == null || fine.isPaid) { return const SizedBox.shrink(); } final isButtonDisabled = state.status == FinePaymentStatus.loading || (!state.useBalance && state.selectedCard == null); return GradientButton( text: state.status == FinePaymentStatus.loading ? 'Обработка...' : 'Оплатить', showArrows: true, height: 56, width: double.infinity, onTap: isButtonDisabled ? null : () { context.read().add(PayFineSubmitted()); }, ); }, ), ), ], ), ], ), ), ), ); } Widget _buildInfoLabel(String text) { return Padding( padding: const EdgeInsets.only(bottom: 6), child: Text( text, style: TextStyle( color: Colors.white.withOpacity(0.7), fontSize: 15, ), ), ); } Widget _buildValueRow(String iconName, String value) { return Row( children: [ Image.asset('assets/icons/$iconName', width: 24, height: 24), const SizedBox(width: 12), Text( value, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500, ), ), ], ); } String _formatDateTime(DateTime dateTime) { final now = DateTime.now(); final localDate = dateTime.toLocal(); final isToday = localDate.year == now.year && localDate.month == now.month && localDate.day == now.day; final yesterday = now.subtract(const Duration(days: 1)); final isYesterday = localDate.year == yesterday.year && localDate.month == yesterday.month && localDate.day == yesterday.day; final time = '${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}'; if (isToday) { return 'Сегодня, $time'; } else if (isYesterday) { return 'Вчера, $time'; } else { const months = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря' ]; return '${localDate.day} ${months[localDate.month - 1]}, $time'; } } }