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,403 @@
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 '../../di/service_locator.dart';
import '../../domain/entities/payment_card.dart';
import '../../domain/usecase/get_payment_cards_usecase.dart';
import '../components/custom_app_bar.dart';
import '../components/gradient_button.dart';
import '../components/payment_option.dart';
import '../components/sheet/payment_method_sheet.dart';
import '../event/payment_confirm_event.dart';
import '../event/payment_method_sheet_event.dart';
import '../state/payment_confirm_state.dart';
import '../viewmodel/payment_confirm_bloc.dart';
import '../viewmodel/payment_method_sheet_bloc.dart';
class PaymentConfirmScreen extends StatelessWidget {
final int orderId;
final List<int> photoIds;
const PaymentConfirmScreen({
super.key,
required this.orderId,
required this.photoIds,
});
@override
Widget build(BuildContext context) {
return _PaymentConfirmScreenContent(orderId: orderId, photoIds: photoIds);
}
}
class _PaymentConfirmScreenContent extends StatelessWidget {
final int orderId;
final List<int> photoIds;
const _PaymentConfirmScreenContent({
required this.orderId,
required this.photoIds,
});
@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 Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: CustomAppBar(title: 'Завершение поездки'),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: BlocConsumer<PaymentConfirmBloc, PaymentConfirmState>(
listenWhen: (previous, current) {
return current.status != previous.status;
},
listener: (context, state) {
if (state.status == PaymentConfirmStatus.success && state.paymentCompleted) {
context.go('/home');
} else if (state.status == PaymentConfirmStatus.failure) {
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 order = state.order;
if (state.status == PaymentConfirmStatus.loading &&
order == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 30),
_buildInfoLabel('Самокат:'),
_buildValueRow(
'qr_icon.png',
"${order?.scooter?.number}",
),
const SizedBox(height: 24),
_buildInfoLabel('Расстояние:'),
_buildValueRow(
'distance_icon.png',
"${order?.mileage} km",
),
const SizedBox(height: 24),
_buildInfoLabel('Начислено баллов:'),
_buildValueRow('points_icon.png', '2 балла'),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
_buildInfoLabel('Стоимость поездки:'),
_buildValueRow(
'money_icon.png',
'17,17 BYN',
),
],
),
),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
_buildInfoLabel('Оплачено:'),
_buildValueRow(
'money_icon.png',
'10,00 BYN',
),
],
),
),
],
),
const SizedBox(height: 40),
// 🔹 БЛОК СУММЫ К ОПЛАТЕ
Center(
child: Column(
children: [
Text(
'Сумма к оплате:',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 16,
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Image.asset(
'assets/icons/money_icon.png',
width: 40,
),
const SizedBox(width: 12),
const Text(
'7,17 BYN',
style: TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
],
),
],
),
),
const SizedBox(height: 40),
// 🔹 КАРТА ОПЛАТЫ
(state.useBalance || state.selectedCard != null)
? Padding(padding: const EdgeInsets.symmetric(horizontal: 20),
child: PaymentOption(
title: state.useBalance ? 'Баланс' : state.selectedCard!.type,
subtitle: state.useBalance
? '${state.userBalance.toStringAsFixed(2)} BYN'
: '****${state.selectedCard!.cardLastNumber}',
isSelected: true,
onTap: () async {
final result = await showModalBottomSheet<dynamic>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (innerContext) => BlocProvider(
create: (context) => PaymentMethodSheetBloc(
getIt<GetPaymentCardsUsecase>(),
)..add(PaymentMethodSheetStarted()),
child: PaymentMethodSheet(
initialSelectedCard: state.useBalance ? null : state.selectedCard,
),
),
);
if (result != null) {
if (result is PaymentCard) {
context.read<PaymentConfirmBloc>().add(PaymentCardChanged(result));
} else if (result == 'balance') {
context.read<PaymentConfirmBloc>().add(SelectBalancePressed());
}
}
},
),
)
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
child: 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: () {
context.pushReplacement(
'/home/payment-method-sheet',
);
},
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),
),
],
),
),
),
),
),
),
],
);
},
),
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child:
BlocConsumer<PaymentConfirmBloc, PaymentConfirmState>(
listener: (context, state) {
if (state.status == PaymentConfirmStatus.success &&
state.paymentCompleted) {
context.go('/home');
} else if (state.status ==
PaymentConfirmStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
state.errorMessage ?? 'Ошибка оплаты',
),
),
);
}
},
builder: (context, state) {
return GradientButton(
text: state.status == PaymentConfirmStatus.loading
? 'Обработка...'
: 'Оплатить',
showArrows: true,
height: 56,
width: double.infinity,
onTap: (state.status == PaymentConfirmStatus.loading || (!state.useBalance && state.selectedCard == null))
? null
: () {
context.read<PaymentConfirmBloc>().add(
PayRide(
cardId: state.useBalance ? null : state.selectedCard?.id,
isBalance: state.useBalance,
orderId: orderId,
photoIds: photoIds,
),
);
},
);
},
),
),
],
),
],
),
),
),
);
}
// Вспомогательный метод для подзаголовков
Widget _buildInfoLabel(String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
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 _formatDuration(int minutes) {
final h = minutes ~/ 60;
final m = minutes % 60;
return '${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}';
}
}