Add test, fixed auto focusing and error handling of invalid dates
This commit is contained in:
+68
-55
@@ -1,20 +1,29 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class CardDetails {
|
||||
CardDetails({required this.cardNumber, required String? securityCode, required this.expirationDate}) {
|
||||
CardDetails({
|
||||
required dynamic cardNumber,
|
||||
required String? securityCode,
|
||||
required this.expirationString,
|
||||
required this.postalCode,
|
||||
}) : _cardNumber = cardNumber {
|
||||
this.securityCode = int.tryParse(securityCode ?? '');
|
||||
checkIsValid();
|
||||
}
|
||||
|
||||
factory CardDetails.blank() {
|
||||
return CardDetails(cardNumber: null, securityCode: null, expirationDate: null);
|
||||
return CardDetails(cardNumber: null, securityCode: null, expirationString: null, postalCode: null);
|
||||
}
|
||||
|
||||
String? cardNumber;
|
||||
String? get cardNumber => _cardNumber?.replaceAll(' ', '');
|
||||
|
||||
set cardNumber(String? num) => _cardNumber = num;
|
||||
|
||||
String? _cardNumber;
|
||||
int? securityCode;
|
||||
String? postalCode;
|
||||
String? expirationDate;
|
||||
String? expirationString;
|
||||
DateTime? expirationDate;
|
||||
bool _complete = false;
|
||||
ValidState _validState = ValidState.blank;
|
||||
int _lastCheckHash = 0;
|
||||
@@ -25,7 +34,8 @@ class CardDetails {
|
||||
return _validState;
|
||||
}
|
||||
|
||||
bool get cardNumberFilled => provider == null || provider?.cardLength == cardNumber?.replaceAll(' ', '').length;
|
||||
bool get cardNumberFilled =>
|
||||
_cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
|
||||
|
||||
bool get isComplete {
|
||||
checkIsValid();
|
||||
@@ -43,12 +53,12 @@ class CardDetails {
|
||||
}
|
||||
|
||||
_lastCheckHash = currentHash;
|
||||
if (cardNumber == null && expirationDate == null && securityCode == null && postalCode == null) {
|
||||
if (_cardNumber == null && expirationString == null && securityCode == null && postalCode == null) {
|
||||
_complete = false;
|
||||
_validState = ValidState.blank;
|
||||
return;
|
||||
}
|
||||
final nums = cardNumber!
|
||||
final nums = _cardNumber!
|
||||
.replaceAll(' ', '')
|
||||
.split('')
|
||||
.map(
|
||||
@@ -60,24 +70,30 @@ class CardDetails {
|
||||
_validState = ValidState.invalidCard;
|
||||
return;
|
||||
}
|
||||
if (cardNumber == null || !cardNumberFilled) {
|
||||
if (_cardNumber == null || !cardNumberFilled) {
|
||||
_complete = false;
|
||||
_validState = ValidState.missingCard;
|
||||
return;
|
||||
}
|
||||
if (expirationDate == null) {
|
||||
if (expirationString == null) {
|
||||
_complete = false;
|
||||
_validState = ValidState.missingDate;
|
||||
return;
|
||||
}
|
||||
final expSplits = expirationDate!.split('/');
|
||||
final expSplits = expirationString!.split('/');
|
||||
if (expSplits.length != 2 || expSplits.last == '') {
|
||||
_complete = false;
|
||||
_validState = ValidState.missingDate;
|
||||
return;
|
||||
}
|
||||
final date = DateTime(2000 + int.parse(expSplits.last),
|
||||
int.parse(expSplits.first[0] == '0' ? expSplits.first[1] : expSplits.first));
|
||||
final month = int.parse(expSplits.first[0] == '0' ? expSplits.first[1] : expSplits.first);
|
||||
if (month < 1 || month > 12) {
|
||||
_complete = false;
|
||||
_validState = ValidState.invalidMonth;
|
||||
return;
|
||||
}
|
||||
final year = 2000 + int.parse(expSplits.last);
|
||||
final date = DateTime(year, month);
|
||||
if (date.isBefore(DateTime.now())) {
|
||||
_complete = false;
|
||||
_validState = ValidState.dateTooEarly;
|
||||
@@ -87,6 +103,7 @@ class CardDetails {
|
||||
_validState = ValidState.dateTooLate;
|
||||
return;
|
||||
}
|
||||
expirationDate = date;
|
||||
if (securityCode == null) {
|
||||
_complete = false;
|
||||
_validState = ValidState.missingCVC;
|
||||
@@ -114,42 +131,42 @@ class CardDetails {
|
||||
}
|
||||
|
||||
int get hash {
|
||||
return Object.hash(cardNumber, expirationDate, securityCode, postalCode);
|
||||
return Object.hash(_cardNumber, expirationString, securityCode, postalCode);
|
||||
}
|
||||
|
||||
void detectCardProvider() {
|
||||
bool found = false;
|
||||
if (cardNumber == null) {
|
||||
if (_cardNumber == null) {
|
||||
return;
|
||||
}
|
||||
for (var cardPvd in providers) {
|
||||
if (cardPvd.INN_VALID_NUMS != null) {
|
||||
if (cardPvd.innValidNums != null) {
|
||||
// trim card number to correct length
|
||||
String trimmedNum = cardNumber!;
|
||||
String innNumStr = '${cardPvd.INN_VALID_NUMS!.first}';
|
||||
String trimmedNum = _cardNumber!;
|
||||
String innNumStr = '${cardPvd.innValidNums!.first}';
|
||||
if (trimmedNum.length > innNumStr.length) {
|
||||
trimmedNum = trimmedNum.substring(0, innNumStr.length);
|
||||
}
|
||||
final num = int.tryParse(trimmedNum);
|
||||
if (num == null) continue;
|
||||
|
||||
if (cardPvd.INN_VALID_NUMS!.contains(num)) {
|
||||
if (cardPvd.innValidNums!.contains(num)) {
|
||||
provider = cardPvd;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cardPvd.INN_VALID_RANGES != null) {
|
||||
if (cardPvd.innValidRanges != null) {
|
||||
// trim card number to correct length
|
||||
String trimmedNum = cardNumber!;
|
||||
String innNumStr = '${cardPvd.INN_VALID_RANGES!.first.low}';
|
||||
String trimmedNum = _cardNumber!;
|
||||
String innNumStr = '${cardPvd.innValidRanges!.first.low}';
|
||||
if (trimmedNum.length > innNumStr.length) {
|
||||
trimmedNum = trimmedNum.substring(0, innNumStr.length);
|
||||
}
|
||||
final num = int.tryParse(trimmedNum);
|
||||
if (num == null) continue;
|
||||
|
||||
if (cardPvd.INN_VALID_RANGES!.any((range) => range.isWithin(num))) {
|
||||
if (cardPvd.innValidRanges!.any((range) => range.isWithin(num))) {
|
||||
provider = cardPvd;
|
||||
found = true;
|
||||
break;
|
||||
@@ -157,13 +174,12 @@ class CardDetails {
|
||||
}
|
||||
}
|
||||
if (!found) provider = null;
|
||||
// print('Got provider $provider');
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Number: "$cardNumber" - Exp: "$expirationDate" CVC: $securityCode Zip: "$postalCode"';
|
||||
}
|
||||
String toString() {
|
||||
return 'Number: "$_cardNumber" - Exp: "$expirationString" CVC: $securityCode Zip: "$postalCode"';
|
||||
}
|
||||
}
|
||||
|
||||
enum ValidState {
|
||||
@@ -173,6 +189,7 @@ enum ValidState {
|
||||
missingCard,
|
||||
invalidCard,
|
||||
missingDate,
|
||||
invalidMonth,
|
||||
dateTooEarly,
|
||||
dateTooLate,
|
||||
missingCVC,
|
||||
@@ -182,31 +199,27 @@ enum ValidState {
|
||||
}
|
||||
|
||||
enum CardProviderID {
|
||||
AmericanExpress,
|
||||
DinersClub,
|
||||
DiscoverCard,
|
||||
Mastercard,
|
||||
JCB,
|
||||
Visa,
|
||||
americanExpress,
|
||||
dinersClub,
|
||||
discoverCard,
|
||||
mastercard,
|
||||
jcb,
|
||||
visa,
|
||||
}
|
||||
|
||||
class CardProvider {
|
||||
CardProviderID id;
|
||||
List<int>? INN_VALID_NUMS;
|
||||
List<Range>? INN_VALID_RANGES;
|
||||
List<int>? innValidNums;
|
||||
List<Range>? innValidRanges;
|
||||
int cardLength;
|
||||
int cvcLength;
|
||||
|
||||
CardProvider(
|
||||
{required this.id,
|
||||
required this.cardLength,
|
||||
required this.cvcLength,
|
||||
this.INN_VALID_NUMS,
|
||||
this.INN_VALID_RANGES}) {
|
||||
{required this.id, required this.cardLength, required this.cvcLength, this.innValidNums, this.innValidRanges}) {
|
||||
// Must provide one or the other
|
||||
assert(INN_VALID_NUMS != null || INN_VALID_RANGES != null);
|
||||
assert(innValidNums != null || innValidRanges != null);
|
||||
// Do not provide empty list of valid nums
|
||||
assert(INN_VALID_NUMS == null || INN_VALID_NUMS!.isNotEmpty);
|
||||
assert(innValidNums == null || innValidNums!.isNotEmpty);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -230,41 +243,41 @@ class Range {
|
||||
|
||||
List<CardProvider> providers = [
|
||||
CardProvider(
|
||||
id: CardProviderID.AmericanExpress,
|
||||
id: CardProviderID.americanExpress,
|
||||
cardLength: 15,
|
||||
cvcLength: 4,
|
||||
INN_VALID_NUMS: [34, 37],
|
||||
innValidNums: [34, 37],
|
||||
),
|
||||
CardProvider(
|
||||
id: CardProviderID.DinersClub,
|
||||
id: CardProviderID.dinersClub,
|
||||
cardLength: 16,
|
||||
cvcLength: 3,
|
||||
INN_VALID_NUMS: [30, 36, 38, 39],
|
||||
innValidNums: [30, 36, 38, 39],
|
||||
),
|
||||
CardProvider(
|
||||
id: CardProviderID.DiscoverCard,
|
||||
id: CardProviderID.discoverCard,
|
||||
cardLength: 16,
|
||||
cvcLength: 3,
|
||||
INN_VALID_NUMS: [60, 65],
|
||||
INN_VALID_RANGES: [Range(low: 644, high: 649)],
|
||||
innValidNums: [60, 65],
|
||||
innValidRanges: [Range(low: 644, high: 649)],
|
||||
),
|
||||
CardProvider(
|
||||
id: CardProviderID.JCB,
|
||||
id: CardProviderID.jcb,
|
||||
cardLength: 16,
|
||||
cvcLength: 3,
|
||||
INN_VALID_NUMS: [35],
|
||||
innValidNums: [35],
|
||||
),
|
||||
CardProvider(
|
||||
id: CardProviderID.Mastercard,
|
||||
id: CardProviderID.mastercard,
|
||||
cardLength: 16,
|
||||
cvcLength: 3,
|
||||
INN_VALID_RANGES: [Range(low: 22, high: 27), Range(low: 51, high: 55)],
|
||||
innValidRanges: [Range(low: 22, high: 27), Range(low: 51, high: 55)],
|
||||
),
|
||||
CardProvider(
|
||||
id: CardProviderID.Visa,
|
||||
id: CardProviderID.visa,
|
||||
cardLength: 16,
|
||||
cvcLength: 3,
|
||||
INN_VALID_NUMS: [4],
|
||||
innValidNums: [4],
|
||||
)
|
||||
];
|
||||
|
||||
|
||||
+24
-20
File diff suppressed because one or more lines are too long
@@ -3,33 +3,44 @@ library stripe_native_card_field;
|
||||
import 'dart:async';
|
||||
import 'card_details.dart';
|
||||
import 'card_provider_icon.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
enum CardEntryStep { number, exp, cvc, postal }
|
||||
|
||||
class CardTextField extends StatefulWidget {
|
||||
const CardTextField({Key? key, required this.width, this.height, this.inputDecoration}) : super(key: key);
|
||||
const CardTextField(
|
||||
{Key? key,
|
||||
required this.onCardDetailsComplete,
|
||||
required this.width,
|
||||
this.height,
|
||||
this.inputDecoration,
|
||||
this.boxDecoration,
|
||||
this.errorBoxDecoration})
|
||||
: super(key: key);
|
||||
|
||||
final InputDecoration? inputDecoration;
|
||||
final InputDecoration? inputDecoration; // TODO unapplied style
|
||||
final BoxDecoration? boxDecoration; // TODO unapplied style
|
||||
final BoxDecoration? errorBoxDecoration; // TODO unapplied style
|
||||
final double width;
|
||||
final void Function(CardDetails) onCardDetailsComplete;
|
||||
final double? height;
|
||||
|
||||
@override
|
||||
State<CardTextField> createState() => _CardTextFieldState();
|
||||
State<CardTextField> createState() => CardTextFieldState();
|
||||
}
|
||||
|
||||
class _CardTextFieldState extends State<CardTextField> {
|
||||
@visibleForTesting
|
||||
class CardTextFieldState extends State<CardTextField> {
|
||||
late TextEditingController _cardNumberController;
|
||||
late TextEditingController _expirationController;
|
||||
late TextEditingController _securityCodeController;
|
||||
late TextEditingController _postalCodeController;
|
||||
|
||||
late FocusNode _cardNumberFocusNode;
|
||||
late FocusNode _expirationFocusNode;
|
||||
late FocusNode _securityCodeFocusNode;
|
||||
late FocusNode _postalCodeFocusNode;
|
||||
late FocusNode cardNumberFocusNode;
|
||||
late FocusNode expirationFocusNode;
|
||||
late FocusNode securityCodeFocusNode;
|
||||
late FocusNode postalCodeFocusNode;
|
||||
|
||||
final double _cardFieldWidth = 180.0;
|
||||
final double _expirationFieldWidth = 70.0;
|
||||
@@ -50,16 +61,16 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
final CardDetails _cardDetails = CardDetails.blank();
|
||||
|
||||
final normalBoxDecoration = BoxDecoration(
|
||||
color: Color(0xfff6f9fc),
|
||||
color: const Color(0xfff6f9fc),
|
||||
border: Border.all(
|
||||
color: Color(0xffdde0e3),
|
||||
color: const Color(0xffdde0e3),
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
);
|
||||
|
||||
final errorBoxDecoration = BoxDecoration(
|
||||
color: Color(0xfff6f9fc),
|
||||
color: const Color(0xfff6f9fc),
|
||||
border: Border.all(
|
||||
color: Colors.red,
|
||||
width: 2.0,
|
||||
@@ -67,8 +78,8 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
);
|
||||
|
||||
final TextStyle _errorTextStyle = TextStyle(color: Colors.red, fontSize: 14);
|
||||
final TextStyle _normalTextStyle = TextStyle(color: Colors.black87, fontSize: 14);
|
||||
final TextStyle _errorTextStyle = const TextStyle(color: Colors.red, fontSize: 14);
|
||||
final TextStyle _normalTextStyle = const TextStyle(color: Colors.black87, fontSize: 14);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -77,10 +88,10 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
_securityCodeController = TextEditingController();
|
||||
_postalCodeController = TextEditingController();
|
||||
|
||||
_cardNumberFocusNode = FocusNode();
|
||||
_expirationFocusNode = FocusNode();
|
||||
_securityCodeFocusNode = FocusNode();
|
||||
_postalCodeFocusNode = FocusNode();
|
||||
cardNumberFocusNode = FocusNode();
|
||||
expirationFocusNode = FocusNode();
|
||||
securityCodeFocusNode = FocusNode();
|
||||
postalCodeFocusNode = FocusNode();
|
||||
|
||||
_currentCardEntryStepController.stream.listen(
|
||||
_onStepChange,
|
||||
@@ -101,9 +112,9 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
_expirationController.dispose();
|
||||
_securityCodeController.dispose();
|
||||
|
||||
_cardNumberFocusNode.dispose();
|
||||
_expirationFocusNode.dispose();
|
||||
_securityCodeFocusNode.dispose();
|
||||
cardNumberFocusNode.dispose();
|
||||
expirationFocusNode.dispose();
|
||||
securityCodeFocusNode.dispose();
|
||||
|
||||
RawKeyboard.instance.removeListener(_backspaceTransitionListener);
|
||||
|
||||
@@ -144,10 +155,11 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
cardDetails: _cardDetails,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
SizedBox(
|
||||
width: _cardFieldWidth,
|
||||
child: TextFormField(
|
||||
focusNode: _cardNumberFocusNode,
|
||||
key: const Key('card_field'),
|
||||
focusNode: cardNumberFocusNode,
|
||||
controller: _cardNumberController,
|
||||
keyboardType: TextInputType.number,
|
||||
style: _isRedText([ValidState.invalidCard, ValidState.missingCard, ValidState.blank])
|
||||
@@ -180,7 +192,7 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
FilteringTextInputFormatter.allow(RegExp('[0-9 ]')),
|
||||
CardNumberInputFormatter(),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Card number',
|
||||
fillColor: Colors.transparent,
|
||||
border: InputBorder.none,
|
||||
@@ -195,36 +207,43 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
curve: Curves.easeOut,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
constraints: _currentStep == CardEntryStep.number
|
||||
? BoxConstraints.loose(Size(400.0, 1.0))
|
||||
: BoxConstraints.tight(Size(0, 0)))),
|
||||
? BoxConstraints.loose(const Size(400.0, 1.0))
|
||||
: BoxConstraints.tight(const Size(0, 0)))),
|
||||
|
||||
// Spacer(flex: _currentStep == CardEntryStep.number ? 100 : 1),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 125),
|
||||
width: _expirationFieldWidth,
|
||||
child: TextFormField(
|
||||
focusNode: _expirationFocusNode,
|
||||
key: const Key('expiration_field'),
|
||||
focusNode: expirationFocusNode,
|
||||
controller: _expirationController,
|
||||
style:
|
||||
_isRedText([ValidState.dateTooLate, ValidState.dateTooEarly, ValidState.missingDate])
|
||||
? _errorTextStyle
|
||||
: _normalTextStyle,
|
||||
style: _isRedText([
|
||||
ValidState.dateTooLate,
|
||||
ValidState.dateTooEarly,
|
||||
ValidState.missingDate,
|
||||
ValidState.invalidMonth
|
||||
])
|
||||
? _errorTextStyle
|
||||
: _normalTextStyle,
|
||||
validator: (content) {
|
||||
if (content == null || content.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
setState(() => _cardDetails.expirationDate = content);
|
||||
setState(() => _cardDetails.expirationString = content);
|
||||
if (_cardDetails.validState == ValidState.dateTooEarly) {
|
||||
_setValidationState('Your card\'s expiration date is in the past.');
|
||||
} else if (_cardDetails.validState == ValidState.dateTooLate) {
|
||||
_setValidationState('Your card\'s expiration year is invalid.');
|
||||
} else if (_cardDetails.validState == ValidState.missingDate) {
|
||||
_setValidationState('You must include your card\'s expiration date.');
|
||||
} else if (_cardDetails.validState == ValidState.invalidMonth) {
|
||||
_setValidationState('Invalid expiration month.');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (str) {
|
||||
setState(() => _cardDetails.expirationDate = str);
|
||||
setState(() => _cardDetails.expirationString = str);
|
||||
if (str.length == 5) {
|
||||
_currentCardEntryStepController.add(CardEntryStep.cvc);
|
||||
}
|
||||
@@ -234,7 +253,7 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
FilteringTextInputFormatter.allow(RegExp('[0-9/]')),
|
||||
CardExpirationFormatter(),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'MM/YY',
|
||||
fillColor: Colors.transparent,
|
||||
border: InputBorder.none,
|
||||
@@ -245,7 +264,8 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
duration: const Duration(milliseconds: 250),
|
||||
width: _securityFieldWidth,
|
||||
child: TextFormField(
|
||||
focusNode: _securityCodeFocusNode,
|
||||
key: const Key('security_field'),
|
||||
focusNode: securityCodeFocusNode,
|
||||
controller: _securityCodeController,
|
||||
style: _isRedText([ValidState.invalidCVC, ValidState.missingCVC])
|
||||
? _errorTextStyle
|
||||
@@ -263,7 +283,7 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
return null;
|
||||
},
|
||||
onChanged: (str) {
|
||||
setState(() => _cardDetails.expirationDate = str);
|
||||
setState(() => _cardDetails.expirationString = str);
|
||||
if (str.length == _cardDetails.provider?.cvcLength) {
|
||||
_currentCardEntryStepController.add(CardEntryStep.postal);
|
||||
}
|
||||
@@ -284,19 +304,18 @@ class _CardTextFieldState extends State<CardTextField> {
|
||||
duration: const Duration(milliseconds: 250),
|
||||
width: _postalFieldWidth,
|
||||
child: TextFormField(
|
||||
focusNode: _postalCodeFocusNode,
|
||||
key: const Key('postal_field'),
|
||||
focusNode: postalCodeFocusNode,
|
||||
controller: _postalCodeController,
|
||||
style: _isRedText([ValidState.invalidZip, ValidState.missingZip])
|
||||
? _errorTextStyle
|
||||
: _normalTextStyle,
|
||||
validator: (content) {
|
||||
print('validate zipcode');
|
||||
if (content == null || content.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
setState(() => _cardDetails.postalCode = content);
|
||||
|
||||
print('checking here\n$_cardDetails');
|
||||
if (_cardDetails.validState == ValidState.invalidZip) {
|
||||
_setValidationState('The postal code you entered is not correct.');
|
||||
} else if (_cardDetails.validState == ValidState.missingZip) {
|
||||
@@ -305,13 +324,11 @@ print('checking here\n$_cardDetails');
|
||||
return null;
|
||||
},
|
||||
onChanged: (str) {
|
||||
print('here');
|
||||
setState(() => _cardDetails.postalCode = str);
|
||||
print(_cardDetails.toString());
|
||||
},
|
||||
onFieldSubmitted: (_) {
|
||||
print('finished');
|
||||
_validateFields();
|
||||
widget.onCardDetailsComplete(_cardDetails);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: _currentStep == CardEntryStep.number ? '' : 'Postal Code',
|
||||
@@ -366,9 +383,8 @@ print('checking here\n$_cardDetails');
|
||||
}
|
||||
|
||||
void _scrollRow(CardEntryStep step) {
|
||||
final dur = Duration(milliseconds: 150);
|
||||
final cur = Curves.easeOut;
|
||||
final fieldLen = widget.width;
|
||||
const dur = Duration(milliseconds: 150);
|
||||
const cur = Curves.easeOut;
|
||||
switch (step) {
|
||||
case CardEntryStep.number:
|
||||
_horizontalScrollController.animateTo(0.0, duration: dur, curve: cur);
|
||||
@@ -398,16 +414,16 @@ print('checking here\n$_cardDetails');
|
||||
});
|
||||
switch (step) {
|
||||
case CardEntryStep.number:
|
||||
_cardNumberFocusNode.requestFocus();
|
||||
cardNumberFocusNode.requestFocus();
|
||||
break;
|
||||
case CardEntryStep.exp:
|
||||
_expirationFocusNode.requestFocus();
|
||||
expirationFocusNode.requestFocus();
|
||||
break;
|
||||
case CardEntryStep.cvc:
|
||||
_securityCodeFocusNode.requestFocus();
|
||||
securityCodeFocusNode.requestFocus();
|
||||
break;
|
||||
case CardEntryStep.postal:
|
||||
_postalCodeFocusNode.requestFocus();
|
||||
postalCodeFocusNode.requestFocus();
|
||||
break;
|
||||
}
|
||||
if (!_isWideFormat) {
|
||||
|
||||
Reference in New Issue
Block a user