new project stable version

This commit is contained in:
2026-05-10 19:11:31 +03:00
commit 3616f84556
391 changed files with 23857 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
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<UploadScooterPhotosUsecase>(),
di.getIt<FinishRideUsecase>(),
),
child: SendPhotoView(orderId: orderId),
);
}
}
class SendPhotoView extends StatefulWidget {
final int orderId;
const SendPhotoView({super.key, required this.orderId});
@override
State<SendPhotoView> createState() => _SendPhotoViewState();
}
class _SendPhotoViewState extends State<SendPhotoView> {
final ImagePicker _imagePicker = ImagePicker();
// Метод для выбора фото (добавляет к существующим)
Future<void> _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<void> _getImage(ImageSource source) async {
final XFile? pickedFile = await _imagePicker.pickImage(
source: source,
imageQuality: 80,
);
if (pickedFile != null && mounted) {
final currentImages = context.read<SendPhotoBloc>().state.selectedImages;
context.read<SendPhotoBloc>().add(
PhotoSelected([...currentImages, pickedFile.path]),
);
}
}
// Метод для удаления конкретного фото
void _removeImage(String path) {
final currentImages = context.read<SendPhotoBloc>().state.selectedImages;
final updatedList = currentImages.where((p) => p != path).toList();
context.read<SendPhotoBloc>().add(PhotoSelected(updatedList));
}
// ✅ Динамический заголовок в зависимости от количества фото
String _getTopText(int photoCount) {
if (photoCount == 0) {
return 'Для завершения аренды\nсфотографируйте самокат';
} else if (photoCount == 1) {
return 'Сделайте фото руля самоката';
} else {
return 'Отправьте фото на проверку';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
child: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 100),
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: [
BlocBuilder<SendPhotoBloc, SendPhotoState>(
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),
// Кнопка "Добавить", если фото меньше лимита (например, 5)
if (photoCount < 2)
GestureDetector(
onTap: _pickImage,
child: _buildAddButton(),
),
],
);
},
),
const SizedBox(height: 200),
// Кнопка отправки
BlocConsumer<SendPhotoBloc, SendPhotoState>(
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;
return GradientButton(
text: 'Отправить',
onTap: isEnabled
? () => context.read<SendPhotoBloc>().add(PhotoUploadSubmitted(widget.orderId))
: null,
enabled: isEnabled,
showArrows: true,
height: 56,
width: double.infinity,
fontSize: 16,
);
},
),
],
),
),
),
),
],
),
),
),
);
}
// миниатюра с крестиком
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,
),
),
);
}
}