Files
be_happy_public/lib/presentation/screens/send_photo_screen.dart

312 lines
11 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: [
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomAppBar(title: "Отправить фото"),
),
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,
),
),
);
}
}