import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import '../../core/app_colors.dart'; import '../../domain/usecase/finish_ride_usecase.dart'; import '../components/custom_app_bar.dart'; import '../components/gradient_button.dart'; import '../viewmodel/send_photo_bloc.dart'; import '../event/send_photo_event.dart'; import '../state/send_photo_state.dart'; import '../../domain/usecase/upload_scooter_photos_usecase.dart'; import '../../di/service_locator.dart' as di; class SendPhotoScreen extends StatelessWidget { final int orderId; const SendPhotoScreen({super.key, required this.orderId}); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => SendPhotoBloc( di.getIt(), di.getIt(), ), child: SendPhotoView(orderId: orderId), ); } } class SendPhotoView extends StatefulWidget { final int orderId; const SendPhotoView({super.key, required this.orderId}); @override State createState() => _SendPhotoViewState(); } class _SendPhotoViewState extends State { final ImagePicker _imagePicker = ImagePicker(); // Метод для выбора фото (добавляет к существующим) Future _pickImage() async { await showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (BuildContext context) { return SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Icons.camera_alt), title: const Text('Сделать фото'), onTap: () { Navigator.pop(context); _getImage(ImageSource.camera); }, ), // ListTile( // leading: const Icon(Icons.photo_library), // title: const Text('Выбрать из галереи'), // onTap: () { // Navigator.pop(context); // _getImage(ImageSource.gallery); // }, // ), ListTile( leading: const Icon(Icons.cancel), title: const Text('Отмена'), onTap: () { Navigator.pop(context); }, ), ], ), ); }, ); } Future _getImage(ImageSource source) async { final XFile? pickedFile = await _imagePicker.pickImage( source: source, imageQuality: 80, ); if (pickedFile != null && mounted) { final currentImages = context.read().state.selectedImages; context.read().add( PhotoSelected([...currentImages, pickedFile.path]), ); } } void _removeImage(String path) { final currentImages = context.read().state.selectedImages; final updatedList = currentImages.where((p) => p != path).toList(); context.read().add(PhotoSelected(updatedList)); } String _getTopText(int photoCount) { if (photoCount == 0) { return 'Для завершения аренды\nсфотографируйте самокат'; } else if (photoCount == 1) { return 'Сделайте фото руля самоката'; } else { return 'Отправьте фото на проверку'; } } @override Widget build(BuildContext context) { final isUploading = context.select( (SendPhotoBloc bloc) => bloc.state.status == SendPhotoStatus.loading, ); return Scaffold( body: Stack( children: [ Container( width: double.infinity, decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg), child: SafeArea( child: Column( children: [ const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: CustomAppBar(title: "Отправить фото"), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 60, ), child: Text( _getTopText( context.select( (SendPhotoBloc bloc) => bloc.state.selectedImages.length, ), ), textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, height: 1.4, ), ), ), // Основной контент центрируем Expanded( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 20, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ... inside SingleChildScrollView -> Column children: BlocBuilder( builder: (context, state) { final photoCount = state.selectedImages.length; return Wrap( spacing: 16, runSpacing: 16, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ // Список выбранных фото ...state.selectedImages.map( (path) => _buildPhotoThumbnail(path), ), const SizedBox(height: 35), // Кнопка "Добавить", если фото меньше лимита if (photoCount < 2) GestureDetector( onTap: _pickImage, child: _buildAddButton(), ), ], ); }, ), const SizedBox(height: 100), // Информационный блок под кнопкой добавления фото Container( width: double.infinity, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), decoration: BoxDecoration( color: Colors.black.withOpacity(0.25), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.08), width: 1, ), ), child: const Text( 'Перед завершением поездки убедитесь, что Вы находитесь в зоне завершения поездки фиолетового цвета.', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 14, height: 1.4, ), ), ), const SizedBox( height: 40, ), // Уменьшено со 200 для гармоничного отступа // Кнопка отправки BlocConsumer( listener: (context, state) { if (state.status == SendPhotoStatus.success) { context.go( '/home/checkout/${widget.orderId}', extra: state.recievedPhotoIds, ); } else if (state.status == SendPhotoStatus.failure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( state.errorMessage ?? 'Ошибка', ), ), ); } }, builder: (context, state) { final photoCount = state.selectedImages.length; final isEnabled = photoCount >= 2 && state.status != SendPhotoStatus.loading; return GradientButton( text: 'Отправить', onTap: isEnabled ? () => context.read().add( PhotoUploadSubmitted(widget.orderId), ) : null, enabled: isEnabled, showArrows: true, height: 56, width: double.infinity, fontSize: 16, ); }, ), ], ), ), ), ), ], ), ), ), // Оверлей загрузки: показывается, пока фото отправляются на сервер if (isUploading) Positioned.fill( child: AbsorbPointer( absorbing: true, child: Container( color: Colors.black.withOpacity(0.45), child: const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: Color(0xFF8BFFAA)), SizedBox(height: 16), Text( 'Загружаем фото...', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ), ], ), ); } // миниатюра с крестиком Widget _buildPhotoThumbnail(String path) { return Stack( clipBehavior: Clip.none, children: [ Container( width: 96, height: 96, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.file(File(path), fit: BoxFit.cover), ), ), // Кнопка удаления (крестик) Positioned( top: -8, right: -8, child: GestureDetector( onTap: () => _removeImage(path), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: Color(0xFF75FBF0), shape: BoxShape.circle, ), child: const Icon( Icons.close, size: 16, color: Color(0xFF242F51), ), ), ), ), ], ); } // Виджет кнопки добавления Widget _buildAddButton() { return Container( width: 96, height: 96, decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( colors: [ Color(0xFF242F51), // полупрозрачный белый Color(0xFF242F51), // ещё более прозрачный ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), boxShadow: [ // Глубокое свечение BoxShadow( color: Color(0xFF8BFFAA).withOpacity(0.5), blurRadius: 50, spreadRadius: 6, offset: Offset.zero, ), // Внутреннее свечение (для объёма) BoxShadow( color: Color(0xFF8BFFAA).withOpacity(0.2), blurRadius: 10, spreadRadius: -2, offset: Offset.zero, ), ], ), child: const Center( child: Icon(Icons.add, size: 32, color: Colors.white), ), ); } }