# Conflicts:
#	lib/presentation/navigation/app_router.dart
#	pubspec.yaml
This commit is contained in:
2026-08-11 00:14:00 +03:00
parent c9b8d143d1
commit 182be1049b
351 changed files with 24566 additions and 22898 deletions

View File

@@ -1,233 +1,233 @@
import 'package:be_happy/presentation/viewmodel/payment_methods_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../core/app_colors.dart';
import '../components/custom_app_bar.dart';
import '../components/card_input_field.dart'; // ← новый импорт
import '../components/utils/card_formatter.dart';
import '../event/payment_methods_event.dart';
import '../viewmodel/add_card_bloc.dart';
import '../event/add_card_event.dart';
import '../state/add_card_state.dart';
class AddCardScreen extends StatelessWidget {
const AddCardScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: BlocListener<AddCardBloc, AddCardState>(
listenWhen: (previous, current) {
print(
'Смена статуса: ${previous.status} -> ${current.status} ${current.errorMessage}');
return previous.status != current.status &&
current.status == AddCardStatus.success;
},
listener: (context, state) {
context.pop(true);
},
child: Container(
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
child: SafeArea(
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
const SizedBox(height: 16),
const CustomAppBar(title: 'Добавление карты'),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFF0A0F2E),
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: '0000 0000 0000 0000',
icon: Icons.credit_card,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(16),
CardNumberFormatter(),
],
letterSpacing: 2,
onChanged: (value) {
final cleanValue = value.replaceAll(' ', '');
context.read<AddCardBloc>().add(CardNumberChanged(cleanValue));
},
);
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: 'MM/YY',
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
CardMonthInputFormatter(),
],
onChanged: (value) {
final cleanValue = value.replaceAll('/', '');
context.read<AddCardBloc>().add(ExpiryDateChanged(cleanValue));
},
);
},
),
),
const SizedBox(width: 12),
Expanded(
child: BlocBuilder<
AddCardBloc,
AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: 'CVV',
obscureText: true,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter
.digitsOnly,
LengthLimitingTextInputFormatter(
3),
],
onChanged: (value) {
context.read<AddCardBloc>().add(
CvvChanged(value));
},
);
},
),
),
],
),
const SizedBox(height: 16),
BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: 'Имя и фамилия на карте',
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization
.words,
onChanged: (value) {
context.read<AddCardBloc>().add(
CardHolderChanged(value));
},
);
},
),
const SizedBox(height: 20),
BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return Container(
height: 46,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Colors.white.withOpacity(0.15),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: state.isFormValid
? () => {
context.read<AddCardBloc>().add(AddCardSubmitted()),
}
: null,
borderRadius: BorderRadius.circular(
24),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment
.center,
children: [
Icon(
Icons.add,
color: state.isFormValid
? const Color(0xFF66E3C4)
: Colors.white
.withOpacity(0.3),
size: 20,
),
const SizedBox(width: 8),
Text(
'Добавить карту',
style: TextStyle(
color: state.isFormValid
? Colors.white
: Colors.white
.withOpacity(0.3),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
);
},
),
],
),
),
],
),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 24),
child: Column(
children: [
const Text(
'Мы не сохраняем данные карты у себя. Оплата происходит '
'через сертифицированный провайдер Беларуси bePaid. '
'Платежная страница системы bePaid отвечает требованиям '
'безопасности передачи данных PCI DSS Level I. '
'Все конфиденциальные данные хранятся в зашифрованном виде.',
style: TextStyle(
color: Colors.white38,
fontSize: 11,
height: 1.5,
),
textAlign: TextAlign.justify,
),
const SizedBox(height: 24),
],
),
),
],
),
),
),
)
);
}
}
import 'package:be_happy/presentation/viewmodel/payment_methods_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../core/app_colors.dart';
import '../components/custom_app_bar.dart';
import '../components/card_input_field.dart'; // ← новый импорт
import '../components/utils/card_formatter.dart';
import '../event/payment_methods_event.dart';
import '../viewmodel/add_card_bloc.dart';
import '../event/add_card_event.dart';
import '../state/add_card_state.dart';
class AddCardScreen extends StatelessWidget {
const AddCardScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: BlocListener<AddCardBloc, AddCardState>(
listenWhen: (previous, current) {
print(
'Смена статуса: ${previous.status} -> ${current.status} ${current.errorMessage}');
return previous.status != current.status &&
current.status == AddCardStatus.success;
},
listener: (context, state) {
context.pop(true);
},
child: Container(
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
child: SafeArea(
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
const SizedBox(height: 16),
const CustomAppBar(title: 'Добавление карты'),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFF0A0F2E),
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: '0000 0000 0000 0000',
icon: Icons.credit_card,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(16),
CardNumberFormatter(),
],
letterSpacing: 2,
onChanged: (value) {
final cleanValue = value.replaceAll(' ', '');
context.read<AddCardBloc>().add(CardNumberChanged(cleanValue));
},
);
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: 'MM/YY',
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
CardMonthInputFormatter(),
],
onChanged: (value) {
final cleanValue = value.replaceAll('/', '');
context.read<AddCardBloc>().add(ExpiryDateChanged(cleanValue));
},
);
},
),
),
const SizedBox(width: 12),
Expanded(
child: BlocBuilder<
AddCardBloc,
AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: 'CVV',
obscureText: true,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter
.digitsOnly,
LengthLimitingTextInputFormatter(
3),
],
onChanged: (value) {
context.read<AddCardBloc>().add(
CvvChanged(value));
},
);
},
),
),
],
),
const SizedBox(height: 16),
BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return CardInputField(
hintText: 'Имя и фамилия на карте',
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization
.words,
onChanged: (value) {
context.read<AddCardBloc>().add(
CardHolderChanged(value));
},
);
},
),
const SizedBox(height: 20),
BlocBuilder<AddCardBloc, AddCardState>(
builder: (context, state) {
return Container(
height: 46,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Colors.white.withOpacity(0.15),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: state.isFormValid
? () => {
context.read<AddCardBloc>().add(AddCardSubmitted()),
}
: null,
borderRadius: BorderRadius.circular(
24),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment
.center,
children: [
Icon(
Icons.add,
color: state.isFormValid
? const Color(0xFF66E3C4)
: Colors.white
.withOpacity(0.3),
size: 20,
),
const SizedBox(width: 8),
Text(
'Добавить карту',
style: TextStyle(
color: state.isFormValid
? Colors.white
: Colors.white
.withOpacity(0.3),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
);
},
),
],
),
),
],
),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 24),
child: Column(
children: [
const Text(
'Мы не сохраняем данные карты у себя. Оплата происходит '
'через сертифицированный провайдер Беларуси bePaid. '
'Платежная страница системы bePaid отвечает требованиям '
'безопасности передачи данных PCI DSS Level I. '
'Все конфиденциальные данные хранятся в зашифрованном виде.',
style: TextStyle(
color: Colors.white38,
fontSize: 11,
height: 1.5,
),
textAlign: TextAlign.justify,
),
const SizedBox(height: 24),
],
),
),
],
),
),
),
)
);
}
}