Files
be_happy_public/lib/presentation/screens/payment_methods_screen.dart
2026-05-29 11:40:55 +03:00

315 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 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../core/app_colors.dart';
import '../../domain/entities/payment_card.dart';
import '../components/custom_app_bar.dart';
import '../event/payment_methods_event.dart';
import '../state/payment_methods_state.dart';
import '../viewmodel/payment_methods_bloc.dart';
class PaymentMethodsScreen extends StatelessWidget {
const PaymentMethodsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(gradient: AppColors.phoneScreenBg),
child: SafeArea(
child: BlocConsumer<PaymentMethodsBloc, PaymentMethodsState>(
listener: (context, state) {
if (state.status == PaymentMethodsStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Ошибка')),
);
}
},
builder: (context, state) {
final isNetworkProcessing = state.status == PaymentMethodsStatus.loading ||
(state.isDeleting ?? false) ||
(state.isSettingMain ?? false);
return Stack(
children: [
Column(
children: [
const SizedBox(height: 16),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: CustomAppBar(title: 'Способы оплаты'),
),
const SizedBox(height: 24),
Expanded(
child: state.cards.isEmpty && state.status == PaymentMethodsStatus.loading
? const Center(
child: CircularProgressIndicator(color: Color(0xFF00D4AA)),
)
: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildBalanceCard(context, state.balance),
const SizedBox(height: 20),
_buildCardsList(context, state),
],
),
),
),
],
),
if (isNetworkProcessing && state.cards.isNotEmpty)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.4),
child: const Center(
child: CircularProgressIndicator(
color: Color(0xFF00D4AA),
),
),
),
),
],
);
},
),
),
),
);
}
Widget _buildBalanceCard(BuildContext context, int balance) {
return Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: AppColors.activeButtonGradient,
borderRadius: BorderRadius.circular(20),
),
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Баланс',
style: TextStyle(color: Color(0xFF0A0F2E), fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
balance.toStringAsFixed(2),
style: TextStyle(color: Color(0xFF0A0F2E), fontSize: 32, fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
Text(
'баллов',
style: TextStyle(color: const Color(0xFF0A0F2E).withOpacity(0.7), fontSize: 14),
),
],
),
const SizedBox(height: 20),
_buildTopUpBalanceButton(context),
],
),
Positioned(
right: -30,
top: -50,
child: Image.asset('assets/icons/card-screen.png', width: 100, height: 100, fit: BoxFit.contain),
),
],
),
);
}
Widget _buildCardsList(BuildContext context, PaymentMethodsState state) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFF0A0F2E).withOpacity(0.65),
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Карты',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
),
const SizedBox(height: 16),
if (state.cards.isEmpty && state.status == PaymentMethodsStatus.success)
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Text('У вас пока нет привязанных карт', style: TextStyle(color: Colors.white70)),
),
...state.cards.asMap().entries.map((entry) {
final index = entry.key;
final card = entry.value;
return Column(
children: [
_CardItem(
card: card,
onDelete: () => context.read<PaymentMethodsBloc>().add(PaymentMethodsDeleteCard(card.id)),
onMakeMain: card.isMain
? null
: () => context.read<PaymentMethodsBloc>().add(PaymentMethodsSetMainCard(card.id)),
),
if (index < state.cards.length - 1) const SizedBox(height: 16),
],
);
}).toList(),
const SizedBox(height: 20),
_buildAddCardButton(context),
],
),
);
}
Widget _buildAddCardButton(BuildContext context) {
return Container(
height: 48,
decoration: BoxDecoration(
color: const Color(0xFF0A0F2E),
borderRadius: BorderRadius.circular(24),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () async {
final isCardAdded = await context.push<bool>('/home/payment-methods/add-card');
if (isCardAdded == true && context.mounted) {
context.read<PaymentMethodsBloc>().add(PaymentMethodsStarted());
}
},
borderRadius: BorderRadius.circular(24),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Icon(Icons.credit_card, color: Color(0xFF00D4AA), size: 24),
SizedBox(width: 12),
Expanded(
child: Text(
'Привязать карту',
style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600),
),
),
Icon(Icons.add, color: Color(0xFF00D4AA), size: 24),
],
),
),
),
),
);
}
Widget _buildTopUpBalanceButton(BuildContext context) {
return Container(
height: 48,
decoration: BoxDecoration(
color: const Color(0xFF0A0F2E),
borderRadius: BorderRadius.circular(24),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => context.go('/home/payment-methods/top-up'),
borderRadius: BorderRadius.circular(24),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Image.asset("assets/icons/money_icon.png", width: 24, height: 24),
SizedBox(width: 12),
Expanded(
child: Text(
'Пополнить баланс',
style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600),
),
),
Icon(Icons.add, color: Color(0xFF00D4AA), size: 24),
],
),
),
),
),
);
}
}
class _CardItem extends StatelessWidget {
final PaymentCard card;
final VoidCallback onDelete;
final VoidCallback? onMakeMain;
const _CardItem({
required this.card,
required this.onDelete,
this.onMakeMain,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Image.asset(
_getDefaultIconPath(card.type),
width: 40,
height: 40,
fit: BoxFit.contain,
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: onMakeMain, // Нажатие на текст карты делает её основной
behavior: HitTestBehavior.opaque,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${card.type} ****${card.cardLastNumber}',
style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
card.isMain ? 'основная' : 'сделать основной',
style: TextStyle(
color: card.isMain ? const Color(0xFF66E3C4) : Colors.white.withOpacity(0.5),
fontSize: 12,
),
),
],
),
),
),
GestureDetector(
onTap: onDelete,
child: const Icon(Icons.close, color: Color(0xFF00D4AA), size: 20),
),
],
);
}
String _getDefaultIconPath(String cardType) {
switch (cardType) {
case 'Belcard': return 'assets/icons/belcard.png';
case 'Visa': return 'assets/icons/visa.png';
case 'Maestro': return 'assets/icons/maestro.png';
case 'Mir': return 'assets/icons/mir.png';
case 'Mastercard': return 'assets/icons/mastercard.png';
default: return 'assets/icons/belcard.png';
}
}
}