proxybuy-flutter/lib/widgets/add_card_bottom_sheet.dart

263 lines
8.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:lba/gen/assets.gen.dart';
import 'package:lba/widgets/button.dart';
import 'package:lba/res/colors.dart';
class AddCardBottomSheet extends StatefulWidget {
const AddCardBottomSheet({super.key});
@override
State<AddCardBottomSheet> createState() => _AddCardBottomSheetState();
}
class _AddCardBottomSheetState extends State<AddCardBottomSheet> {
final TextEditingController _cardNumberController = TextEditingController();
final TextEditingController _expiryDateController = TextEditingController();
bool _isCardNumberValid = false;
bool _isExpiryDateValid = false;
final FocusNode _cardNumberFocus = FocusNode();
final FocusNode _expiryDateFocus = FocusNode();
Color _borderColor = AppColors.greyBorder;
@override
void initState() {
super.initState();
_cardNumberController.addListener(_validateCardNumber);
_expiryDateController.addListener(_validateExpiryDate);
_cardNumberFocus.addListener(() {
setState(() {
_borderColor = _cardNumberFocus.hasFocus || _expiryDateFocus.hasFocus
? Theme.of(context).primaryColor
: AppColors.greyBorder;
});
});
_expiryDateFocus.addListener(() {
setState(() {
_borderColor = _cardNumberFocus.hasFocus || _expiryDateFocus.hasFocus
? Theme.of(context).primaryColor
: AppColors.greyBorder;
});
});
}
@override
void dispose() {
_cardNumberController.removeListener(_validateCardNumber);
_expiryDateController.removeListener(_validateExpiryDate);
_cardNumberController.dispose();
_expiryDateController.dispose();
_cardNumberFocus.dispose();
_expiryDateFocus.dispose();
super.dispose();
}
void _validateCardNumber() {
final text = _cardNumberController.text.replaceAll('-', '');
setState(() {
_isCardNumberValid = text.length == 16;
});
}
void _validateExpiryDate() {
final text = _expiryDateController.text;
if (text.length == 5) {
final parts = text.split('/');
if (parts.length == 2) {
final month = int.tryParse(parts[0]);
final year = int.tryParse(parts[1]);
if (month != null && year != null) {
final now = DateTime.now();
final currentYear = now.year % 100;
final currentMonth = now.month;
if (month >= 1 &&
month <= 12 &&
(year > currentYear ||
(year == currentYear && month >= currentMonth))) {
setState(() {
_isExpiryDateValid = true;
});
return;
}
}
}
}
setState(() {
_isExpiryDateValid = false;
});
}
bool get _isFormValid => _isCardNumberValid && _isExpiryDateValid;
void _addCard() {
if (_isFormValid) {
Navigator.pop(context, _cardNumberController.text);
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Container(
padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Add card',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppColors.textPrice,
),
),
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 24),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 55,
decoration: BoxDecoration(
border: Border.all(color: _borderColor, width: 1.5),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: SvgPicture.asset(Assets.icons.card.path, width: 24, height: 24),
),
Expanded(
child: TextFormField(
controller: _cardNumberController,
focusNode: _cardNumberFocus,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(16),
_CardNumberInputFormatter(),
],
decoration: const InputDecoration(
hintText: '1234-5678-1234-5678',
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 15),
),
),
),
SizedBox(
width: 80,
child: TextFormField(
controller: _expiryDateController,
focusNode: _expiryDateFocus,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
_ExpiryDateInputFormatter(),
],
decoration: const InputDecoration(
hintText: 'MM/YY',
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
),
),
),
],
),
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 50,
child: Button(
text: 'Add card',
onPressed: _isFormValid ? _addCard : () {},
color: _isFormValid
? AppColors.errorColor
: AppColors.errorColor.withOpacity(0.5),
),
),
],
),
),
);
}
}
class _CardNumberInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var text = newValue.text;
if (newValue.selection.baseOffset == 0) {
return newValue;
}
var buffer = StringBuffer();
for (int i = 0; i < text.length; i++) {
buffer.write(text[i]);
var nonZeroIndex = i + 1;
if (nonZeroIndex % 4 == 0 && nonZeroIndex != text.length) {
buffer.write('-');
}
}
var string = buffer.toString();
return newValue.copyWith(
text: string,
selection: TextSelection.collapsed(offset: string.length));
}
}
class _ExpiryDateInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var newText = newValue.text;
if (newValue.selection.baseOffset == 0) {
return newValue;
}
if (oldValue.text.endsWith('/') &&
oldValue.text.length > newValue.text.length) {
newText = newText.substring(0, newText.length - 1);
}
var buffer = StringBuffer();
for (int i = 0; i < newText.length; i++) {
buffer.write(newText[i]);
if (i == 1 && newText.length > 2 && !newText.contains('/')) {
buffer.write('/');
}
}
var string = buffer.toString();
return newValue.copyWith(
text: string.substring(0, string.length > 5 ? 5 : string.length),
selection: TextSelection.collapsed(
offset: string.length > 5 ? 5 : string.length,
),
);
}
}