Compare commits
2 Commits
645d0749da
...
1c918f72f9
Author | SHA1 | Date | |
---|---|---|---|
|
1c918f72f9 | ||
|
4b0e4a98de |
|
@ -1,3 +1,8 @@
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
- Fix Web, invalid call to `Platform.isAndroid`
|
||||||
|
- Analysis issues fixed for pub points
|
||||||
|
|
||||||
## 0.0.4
|
## 0.0.4
|
||||||
|
|
||||||
- Fix for focus and soft keyboard on mobile devices
|
- Fix for focus and soft keyboard on mobile devices
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stripe_native_card_field/card_details.dart';
|
import 'package:stripe_native_card_field/card_details.dart';
|
||||||
|
@ -19,7 +18,8 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'Native Stripe Field Demo',
|
title: 'Native Stripe Field Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||||
|
@ -66,9 +66,10 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
print(details);
|
print(details);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
textStyle: TextStyle(fontFamily: 'Lato', color: Colors.tealAccent),
|
textStyle:
|
||||||
hintTextStyle: TextStyle(fontFamily: 'Lato', color: Colors.teal),
|
const TextStyle(fontFamily: 'Lato', color: Colors.tealAccent),
|
||||||
errorTextStyle: TextStyle(color: Colors.purpleAccent),
|
hintTextStyle: const TextStyle(fontFamily: 'Lato', color: Colors.teal),
|
||||||
|
errorTextStyle: const TextStyle(color: Colors.purpleAccent),
|
||||||
boxDecoration: BoxDecoration(
|
boxDecoration: BoxDecoration(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
|
|
|
@ -58,12 +58,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
print(details);
|
print(details);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// textStyle: TextStyle(fontSize: 24.0),
|
|
||||||
// cardFieldWidth: 260,
|
|
||||||
// expFieldWidth: 100.0,
|
|
||||||
// securityFieldWidth: 60.0,
|
|
||||||
// postalFieldWidth: 130.0,
|
|
||||||
// iconSize: Size(50.0, 35.0),
|
|
||||||
overrideValidState: state,
|
overrideValidState: state,
|
||||||
errorText: errorText,
|
errorText: errorText,
|
||||||
),
|
),
|
||||||
|
|
|
@ -18,7 +18,11 @@ class CardDetails {
|
||||||
/// Sets every field to null, a default
|
/// Sets every field to null, a default
|
||||||
/// `CardDetails` when nothing has been entered.
|
/// `CardDetails` when nothing has been entered.
|
||||||
factory CardDetails.blank() {
|
factory CardDetails.blank() {
|
||||||
return CardDetails(cardNumber: null, securityCode: null, expirationString: null, postalCode: null);
|
return CardDetails(
|
||||||
|
cardNumber: null,
|
||||||
|
securityCode: null,
|
||||||
|
expirationString: null,
|
||||||
|
postalCode: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the CardNumber as a `String` with the spaces removed.
|
/// Returns the CardNumber as a `String` with the spaces removed.
|
||||||
|
@ -51,8 +55,9 @@ class CardDetails {
|
||||||
/// Returns true if `_cardNumber` is null, or
|
/// Returns true if `_cardNumber` is null, or
|
||||||
/// if the _cardNumber matches the detected `provider`'s
|
/// if the _cardNumber matches the detected `provider`'s
|
||||||
/// card lenght, defaulting to 16.
|
/// card lenght, defaulting to 16.
|
||||||
bool get cardNumberFilled =>
|
bool get cardNumberFilled => _cardNumber == null
|
||||||
_cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
|
? false
|
||||||
|
: (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
|
||||||
|
|
||||||
/// Returns true if all details are complete and valid
|
/// Returns true if all details are complete and valid
|
||||||
/// otherwise, return false.
|
/// otherwise, return false.
|
||||||
|
@ -78,7 +83,10 @@ class CardDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastCheckHash = currentHash;
|
_lastCheckHash = currentHash;
|
||||||
if (_cardNumber == null && expirationString == null && securityCode == null && postalCode == null) {
|
if (_cardNumber == null &&
|
||||||
|
expirationString == null &&
|
||||||
|
securityCode == null &&
|
||||||
|
postalCode == null) {
|
||||||
_complete = false;
|
_complete = false;
|
||||||
_validState = CardDetailsValidState.blank;
|
_validState = CardDetailsValidState.blank;
|
||||||
return;
|
return;
|
||||||
|
@ -111,7 +119,8 @@ class CardDetails {
|
||||||
_validState = CardDetailsValidState.missingDate;
|
_validState = CardDetailsValidState.missingDate;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final month = 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) {
|
if (month < 1 || month > 12) {
|
||||||
_complete = false;
|
_complete = false;
|
||||||
_validState = CardDetailsValidState.invalidMonth;
|
_validState = CardDetailsValidState.invalidMonth;
|
||||||
|
@ -123,7 +132,8 @@ class CardDetails {
|
||||||
_complete = false;
|
_complete = false;
|
||||||
_validState = CardDetailsValidState.dateTooEarly;
|
_validState = CardDetailsValidState.dateTooEarly;
|
||||||
return;
|
return;
|
||||||
} else if (date.isAfter(DateTime.now().add(const Duration(days: 365 * 50)))) {
|
} else if (date
|
||||||
|
.isAfter(DateTime.now().add(const Duration(days: 365 * 50)))) {
|
||||||
_complete = false;
|
_complete = false;
|
||||||
_validState = CardDetailsValidState.dateTooLate;
|
_validState = CardDetailsValidState.dateTooLate;
|
||||||
return;
|
return;
|
||||||
|
@ -282,7 +292,11 @@ class CardProvider {
|
||||||
int cvcLength;
|
int cvcLength;
|
||||||
|
|
||||||
CardProvider(
|
CardProvider(
|
||||||
{required this.id, required this.cardLength, required this.cvcLength, this.innValidNums, this.innValidRanges}) {
|
{required this.id,
|
||||||
|
required this.cardLength,
|
||||||
|
required this.cvcLength,
|
||||||
|
this.innValidNums,
|
||||||
|
this.innValidRanges}) {
|
||||||
// Must provide one or the other
|
// Must provide one or the other
|
||||||
assert(innValidNums != null || innValidRanges != null);
|
assert(innValidNums != null || innValidRanges != null);
|
||||||
// Do not provide empty list of valid nums
|
// Do not provide empty list of valid nums
|
||||||
|
|
|
@ -7,7 +7,12 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||||
///
|
///
|
||||||
/// To see a list of supported card providers, see `CardDetails.provider`.
|
/// To see a list of supported card providers, see `CardDetails.provider`.
|
||||||
class CardProviderIcon extends StatefulWidget {
|
class CardProviderIcon extends StatefulWidget {
|
||||||
const CardProviderIcon({required this.cardDetails, this.size, this.defaultCardColor, this.errorCardColor, super.key});
|
const CardProviderIcon(
|
||||||
|
{required this.cardDetails,
|
||||||
|
this.size,
|
||||||
|
this.defaultCardColor,
|
||||||
|
this.errorCardColor,
|
||||||
|
super.key});
|
||||||
|
|
||||||
final CardDetails? cardDetails;
|
final CardDetails? cardDetails;
|
||||||
final Size? size;
|
final Size? size;
|
||||||
|
@ -29,9 +34,9 @@ class _CardProviderIconState extends State<CardProviderIcon> {
|
||||||
|
|
||||||
cardProviderSvg = {
|
cardProviderSvg = {
|
||||||
'credit-card':
|
'credit-card':
|
||||||
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="${defaultCardColor}" stroke="${defaultCardColor}" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H21.4C21.7314 5 22 5.26863 22 5.6V9M2 9V18.4C2 18.7314 2.26863 19 2.6 19H21.4C21.7314 19 22 18.7314 22 18.4V9M2 9H22" stroke="${defaultCardColor}" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="$defaultCardColor" stroke="$defaultCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H21.4C21.7314 5 22 5.26863 22 5.6V9M2 9V18.4C2 18.7314 2.26863 19 2.6 19H21.4C21.7314 19 22 18.7314 22 18.4V9M2 9H22" stroke="$defaultCardColor" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||||
'error':
|
'error':
|
||||||
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3L21 21" stroke="${errorCardColor}" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="${errorCardColor}" stroke="${errorCardColor}" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 19H2.6C2.26863 19 2 18.7314 2 18.4V9H8.5" stroke="${errorCardColor}" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H4.5" stroke="${errorCardColor}" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 9H22V17" stroke="${errorCardColor}" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 9V5.6C22 5.26863 21.7314 5 21.4 5H10" stroke="${errorCardColor}" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
'<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3L21 21" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 15C7.55228 15 8 14.5523 8 14C8 13.4477 7.55228 13 7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15Z" fill="$errorCardColor" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 19H2.6C2.26863 19 2 18.7314 2 18.4V9H8.5" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9V5.6C2 5.26863 2.26863 5 2.6 5H4.5" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 9H22V17" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 9V5.6C22 5.26863 21.7314 5 21.4 5H10" stroke="$errorCardColor" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||||
CardProviderID.discoverCard.name:
|
CardProviderID.discoverCard.name:
|
||||||
'<svg height="500" viewBox="0 0 780 500" width="780" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m54.992 0c-30.365 0-54.992 24.63-54.992 55.004v390.992c0 30.38 24.619 55.004 54.992 55.004h670.016c30.365 0 54.992-24.63 54.992-55.004v-390.992c0-30.38-24.619-55.004-54.992-55.004z" fill="#4d4d4d"/><path d="m327.152 161.893c8.837 0 16.248 1.784 25.268 6.09v22.751c-8.544-7.863-15.955-11.154-25.756-11.154-19.264 0-34.414 15.015-34.414 34.05 0 20.075 14.681 34.196 35.37 34.196 9.312 0 16.586-3.12 24.8-10.857v22.763c-9.341 4.14-16.911 5.776-25.756 5.776-31.278 0-55.582-22.596-55.582-51.737 0-28.826 24.951-51.878 56.07-51.878zm-97.113.627c11.546 0 22.11 3.72 30.943 10.994l-10.748 13.248c-5.35-5.646-10.41-8.028-16.564-8.028-8.853 0-15.3 4.745-15.3 10.989 0 5.354 3.619 8.188 15.944 12.482 23.365 8.044 30.29 15.176 30.29 30.926 0 19.193-14.976 32.553-36.32 32.553-15.63 0-26.994-5.795-36.458-18.872l13.268-12.03c4.73 8.61 12.622 13.222 22.42 13.222 9.163 0 15.947-5.952 15.947-13.984 0-4.164-2.055-7.734-6.158-10.258-2.066-1.195-6.158-2.977-14.2-5.647-19.291-6.538-25.91-13.527-25.91-27.185 0-16.225 14.214-28.41 32.846-28.41zm234.723 1.728h22.437l28.084 66.592 28.446-66.592h22.267l-45.494 101.686h-11.053zm-397.348.152h30.15c33.312 0 56.534 20.382 56.534 49.641 0 14.59-7.104 28.696-19.118 38.057-10.108 7.901-21.626 11.445-37.574 11.445h-29.992zm96.135 0h20.54v99.143h-20.54zm411.734 0h58.252v16.8h-37.725v22.005h36.336v16.791h-36.336v26.762h37.726v16.785h-58.252v-99.143zm71.858 0h30.455c23.69 0 37.265 10.71 37.265 29.272 0 15.18-8.514 25.14-23.986 28.105l33.148 41.766h-25.26l-28.429-39.828h-2.678v39.828h-20.515zm20.515 15.616v30.025h6.002c13.117 0 20.069-5.362 20.069-15.328 0-9.648-6.954-14.697-19.745-14.697zm-579.716 1.183v65.559h5.512c13.273 0 21.656-2.394 28.11-7.88 7.103-5.955 11.376-15.465 11.376-24.98 0-9.499-4.273-18.725-11.376-24.681-6.785-5.78-14.837-8.018-28.11-8.018z" fill="#fff"/><path d="m415.13 161.21c30.941 0 56.022 23.58 56.022 52.709v.033c0 29.13-25.081 52.742-56.021 52.742s-56.022-23.613-56.022-52.742v-.033c0-29.13 25.082-52.71 56.022-52.71zm364.85 127.15c-26.05 18.33-221.08 149.34-558.75 212.62h503.76c30.365 0 54.992-24.63 54.992-55.004v-157.62z" fill="#f47216"/></g></svg>',
|
'<svg height="500" viewBox="0 0 780 500" width="780" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m54.992 0c-30.365 0-54.992 24.63-54.992 55.004v390.992c0 30.38 24.619 55.004 54.992 55.004h670.016c30.365 0 54.992-24.63 54.992-55.004v-390.992c0-30.38-24.619-55.004-54.992-55.004z" fill="#4d4d4d"/><path d="m327.152 161.893c8.837 0 16.248 1.784 25.268 6.09v22.751c-8.544-7.863-15.955-11.154-25.756-11.154-19.264 0-34.414 15.015-34.414 34.05 0 20.075 14.681 34.196 35.37 34.196 9.312 0 16.586-3.12 24.8-10.857v22.763c-9.341 4.14-16.911 5.776-25.756 5.776-31.278 0-55.582-22.596-55.582-51.737 0-28.826 24.951-51.878 56.07-51.878zm-97.113.627c11.546 0 22.11 3.72 30.943 10.994l-10.748 13.248c-5.35-5.646-10.41-8.028-16.564-8.028-8.853 0-15.3 4.745-15.3 10.989 0 5.354 3.619 8.188 15.944 12.482 23.365 8.044 30.29 15.176 30.29 30.926 0 19.193-14.976 32.553-36.32 32.553-15.63 0-26.994-5.795-36.458-18.872l13.268-12.03c4.73 8.61 12.622 13.222 22.42 13.222 9.163 0 15.947-5.952 15.947-13.984 0-4.164-2.055-7.734-6.158-10.258-2.066-1.195-6.158-2.977-14.2-5.647-19.291-6.538-25.91-13.527-25.91-27.185 0-16.225 14.214-28.41 32.846-28.41zm234.723 1.728h22.437l28.084 66.592 28.446-66.592h22.267l-45.494 101.686h-11.053zm-397.348.152h30.15c33.312 0 56.534 20.382 56.534 49.641 0 14.59-7.104 28.696-19.118 38.057-10.108 7.901-21.626 11.445-37.574 11.445h-29.992zm96.135 0h20.54v99.143h-20.54zm411.734 0h58.252v16.8h-37.725v22.005h36.336v16.791h-36.336v26.762h37.726v16.785h-58.252v-99.143zm71.858 0h30.455c23.69 0 37.265 10.71 37.265 29.272 0 15.18-8.514 25.14-23.986 28.105l33.148 41.766h-25.26l-28.429-39.828h-2.678v39.828h-20.515zm20.515 15.616v30.025h6.002c13.117 0 20.069-5.362 20.069-15.328 0-9.648-6.954-14.697-19.745-14.697zm-579.716 1.183v65.559h5.512c13.273 0 21.656-2.394 28.11-7.88 7.103-5.955 11.376-15.465 11.376-24.98 0-9.499-4.273-18.725-11.376-24.681-6.785-5.78-14.837-8.018-28.11-8.018z" fill="#fff"/><path d="m415.13 161.21c30.941 0 56.022 23.58 56.022 52.709v.033c0 29.13-25.081 52.742-56.021 52.742s-56.022-23.613-56.022-52.742v-.033c0-29.13 25.082-52.71 56.022-52.71zm364.85 127.15c-26.05 18.33-221.08 149.34-558.75 212.62h503.76c30.365 0 54.992-24.63 54.992-55.004v-157.62z" fill="#f47216"/></g></svg>',
|
||||||
CardProviderID.americanExpress.name:
|
CardProviderID.americanExpress.name:
|
||||||
|
|
|
@ -59,7 +59,8 @@ class CardTextField extends StatefulWidget {
|
||||||
assert(stripePublishableKey!.startsWith('pk_'));
|
assert(stripePublishableKey!.startsWith('pk_'));
|
||||||
if (kReleaseMode && !stripePublishableKey!.startsWith('pk_live_')) {
|
if (kReleaseMode && !stripePublishableKey!.startsWith('pk_live_')) {
|
||||||
log('StripeNativeCardField: *WARN* You are not using a live publishableKey in production.');
|
log('StripeNativeCardField: *WARN* You are not using a live publishableKey in production.');
|
||||||
} else if ((kDebugMode || kProfileMode) && stripePublishableKey!.startsWith('pk_live_')) {
|
} else if ((kDebugMode || kProfileMode) &&
|
||||||
|
stripePublishableKey!.startsWith('pk_live_')) {
|
||||||
log('StripeNativeCardField: *WARN* You are using a live stripe key in a debug environment, proceed with caution!');
|
log('StripeNativeCardField: *WARN* You are using a live stripe key in a debug environment, proceed with caution!');
|
||||||
log('StripeNativeCardField: *WARN* Ideally you should be using your test keys whenever not in production.');
|
log('StripeNativeCardField: *WARN* Ideally you should be using your test keys whenever not in production.');
|
||||||
}
|
}
|
||||||
|
@ -198,7 +199,7 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
|
|
||||||
String? _validationErrorText;
|
String? _validationErrorText;
|
||||||
bool _showBorderError = false;
|
bool _showBorderError = false;
|
||||||
final _isMobile = Platform.isAndroid || Platform.isIOS;
|
final _isMobile = kIsWeb ? false : Platform.isAndroid || Platform.isIOS;
|
||||||
|
|
||||||
/// If a request to Stripe is being made
|
/// If a request to Stripe is being made
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
|
@ -220,9 +221,12 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
|
|
||||||
// No way to get backspace events on soft keyboards, so add invisible character to detect delete
|
// No way to get backspace events on soft keyboards, so add invisible character to detect delete
|
||||||
_cardNumberController = TextEditingController();
|
_cardNumberController = TextEditingController();
|
||||||
_expirationController = TextEditingController(text: _isMobile ? '\u200b' : '');
|
_expirationController =
|
||||||
_securityCodeController = TextEditingController(text: _isMobile ? '\u200b' : '');
|
TextEditingController(text: _isMobile ? '\u200b' : '');
|
||||||
_postalCodeController = TextEditingController(text: _isMobile ? '\u200b' : '');
|
_securityCodeController =
|
||||||
|
TextEditingController(text: _isMobile ? '\u200b' : '');
|
||||||
|
_postalCodeController =
|
||||||
|
TextEditingController(text: _isMobile ? '\u200b' : '');
|
||||||
|
|
||||||
// Otherwise, use `RawKeyboard` listener
|
// Otherwise, use `RawKeyboard` listener
|
||||||
if (!_isMobile) {
|
if (!_isMobile) {
|
||||||
|
@ -234,10 +238,14 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
securityCodeFocusNode = FocusNode();
|
securityCodeFocusNode = FocusNode();
|
||||||
postalCodeFocusNode = FocusNode();
|
postalCodeFocusNode = FocusNode();
|
||||||
|
|
||||||
_errorTextStyle = const TextStyle(color: Colors.red, fontSize: 14, inherit: true)
|
_errorTextStyle =
|
||||||
|
const TextStyle(color: Colors.red, fontSize: 14, inherit: true)
|
||||||
.merge(widget.errorTextStyle ?? widget.textStyle);
|
.merge(widget.errorTextStyle ?? widget.textStyle);
|
||||||
_normalTextStyle = const TextStyle(color: Colors.black87, fontSize: 14, inherit: true).merge(widget.textStyle);
|
_normalTextStyle =
|
||||||
_hintTextSyle = const TextStyle(color: Colors.black54, fontSize: 14, inherit: true)
|
const TextStyle(color: Colors.black87, fontSize: 14, inherit: true)
|
||||||
|
.merge(widget.textStyle);
|
||||||
|
_hintTextSyle =
|
||||||
|
const TextStyle(color: Colors.black54, fontSize: 14, inherit: true)
|
||||||
.merge(widget.hintTextStyle ?? widget.textStyle);
|
.merge(widget.hintTextStyle ?? widget.textStyle);
|
||||||
|
|
||||||
_normalBoxDecoration = BoxDecoration(
|
_normalBoxDecoration = BoxDecoration(
|
||||||
|
@ -280,15 +288,31 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
_onStepChange,
|
_onStepChange,
|
||||||
);
|
);
|
||||||
|
|
||||||
isWideFormat =
|
isWideFormat = widget.width >=
|
||||||
widget.width >= _cardFieldWidth + _expirationFieldWidth + _securityFieldWidth + _postalFieldWidth + 60.0;
|
_cardFieldWidth +
|
||||||
|
_expirationFieldWidth +
|
||||||
|
_securityFieldWidth +
|
||||||
|
_postalFieldWidth +
|
||||||
|
60.0;
|
||||||
if (isWideFormat) {
|
if (isWideFormat) {
|
||||||
_internalFieldWidth = widget.width + _postalFieldWidth + 35;
|
_internalFieldWidth = widget.width + _postalFieldWidth + 35;
|
||||||
_expanderWidthExpanded = widget.width - _cardFieldWidth - _expirationFieldWidth - _securityFieldWidth - 35;
|
_expanderWidthExpanded = widget.width -
|
||||||
_expanderWidthCollapsed =
|
_cardFieldWidth -
|
||||||
widget.width - _cardFieldWidth - _expirationFieldWidth - _securityFieldWidth - _postalFieldWidth - 70;
|
_expirationFieldWidth -
|
||||||
|
_securityFieldWidth -
|
||||||
|
35;
|
||||||
|
_expanderWidthCollapsed = widget.width -
|
||||||
|
_cardFieldWidth -
|
||||||
|
_expirationFieldWidth -
|
||||||
|
_securityFieldWidth -
|
||||||
|
_postalFieldWidth -
|
||||||
|
70;
|
||||||
} else {
|
} else {
|
||||||
_internalFieldWidth = _cardFieldWidth + _expirationFieldWidth + _securityFieldWidth + _postalFieldWidth + 80;
|
_internalFieldWidth = _cardFieldWidth +
|
||||||
|
_expirationFieldWidth +
|
||||||
|
_securityFieldWidth +
|
||||||
|
_postalFieldWidth +
|
||||||
|
80;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -314,8 +338,10 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if ((widget.errorText != null || widget.overrideValidState != null) &&
|
if ((widget.errorText != null || widget.overrideValidState != null) &&
|
||||||
Object.hashAll([widget.errorText, widget.overrideValidState]) != _prevErrorOverrideHash) {
|
Object.hashAll([widget.errorText, widget.overrideValidState]) !=
|
||||||
_prevErrorOverrideHash = Object.hashAll([widget.errorText, widget.overrideValidState]);
|
_prevErrorOverrideHash) {
|
||||||
|
_prevErrorOverrideHash =
|
||||||
|
Object.hashAll([widget.errorText, widget.overrideValidState]);
|
||||||
_validateFields();
|
_validateFields();
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
|
@ -332,9 +358,11 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
// Enable scrolling on mobile and if its narrow (not all fields visible)
|
// Enable scrolling on mobile and if its narrow (not all fields visible)
|
||||||
onHorizontalDragUpdate: (details) {
|
onHorizontalDragUpdate: (details) {
|
||||||
const minOffset = 0.0;
|
const minOffset = 0.0;
|
||||||
final maxOffset = _horizontalScrollController.position.maxScrollExtent;
|
final maxOffset =
|
||||||
|
_horizontalScrollController.position.maxScrollExtent;
|
||||||
if (!_isMobile || isWideFormat) return;
|
if (!_isMobile || isWideFormat) return;
|
||||||
final newOffset = _horizontalScrollController.offset - details.delta.dx;
|
final newOffset =
|
||||||
|
_horizontalScrollController.offset - details.delta.dx;
|
||||||
|
|
||||||
if (newOffset < minOffset) {
|
if (newOffset < minOffset) {
|
||||||
_horizontalScrollController.jumpTo(minOffset);
|
_horizontalScrollController.jumpTo(minOffset);
|
||||||
|
@ -345,19 +373,24 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onHorizontalDragEnd: (details) {
|
onHorizontalDragEnd: (details) {
|
||||||
if (!_isMobile || isWideFormat || details.primaryVelocity == null) return;
|
if (!_isMobile || isWideFormat || details.primaryVelocity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const dur = Duration(milliseconds: 300);
|
const dur = Duration(milliseconds: 300);
|
||||||
const cur = Curves.ease;
|
const cur = Curves.ease;
|
||||||
|
|
||||||
// final max = _horizontalScrollController.position.maxScrollExtent;
|
// final max = _horizontalScrollController.position.maxScrollExtent;
|
||||||
final newOffset = _horizontalScrollController.offset - details.primaryVelocity! * 0.15;
|
final newOffset = _horizontalScrollController.offset -
|
||||||
_horizontalScrollController.animateTo(newOffset, curve: cur, duration: dur);
|
details.primaryVelocity! * 0.15;
|
||||||
|
_horizontalScrollController.animateTo(newOffset,
|
||||||
|
curve: cur, duration: dur);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
height: widget.height ?? 60.0,
|
height: widget.height ?? 60.0,
|
||||||
decoration: _showBorderError ? _errorBoxDecoration : _normalBoxDecoration,
|
decoration:
|
||||||
|
_showBorderError ? _errorBoxDecoration : _normalBoxDecoration,
|
||||||
child: ClipRect(
|
child: ClipRect(
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
@ -371,7 +404,8 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 6.0),
|
||||||
child: CardProviderIcon(
|
child: CardProviderIcon(
|
||||||
cardDetails: _cardDetails,
|
cardDetails: _cardDetails,
|
||||||
size: widget.iconSize,
|
size: widget.iconSize,
|
||||||
|
@ -398,27 +432,36 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
_cardDetails.cardNumber = content;
|
_cardDetails.cardNumber = content;
|
||||||
if (_cardDetails.validState == CardDetailsValidState.invalidCard) {
|
if (_cardDetails.validState ==
|
||||||
_setValidationState('Your card number is invalid.');
|
CardDetailsValidState.invalidCard) {
|
||||||
} else if (_cardDetails.validState == CardDetailsValidState.missingCard) {
|
_setValidationState(
|
||||||
_setValidationState('Your card number is incomplete.');
|
'Your card number is invalid.');
|
||||||
|
} else if (_cardDetails.validState ==
|
||||||
|
CardDetailsValidState.missingCard) {
|
||||||
|
_setValidationState(
|
||||||
|
'Your card number is incomplete.');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onChanged: (str) {
|
onChanged: (str) {
|
||||||
final numbers = str.replaceAll(' ', '');
|
final numbers = str.replaceAll(' ', '');
|
||||||
setState(() => _cardDetails.cardNumber = numbers);
|
setState(
|
||||||
|
() => _cardDetails.cardNumber = numbers);
|
||||||
if (str.length <= _cardDetails.maxINNLength) {
|
if (str.length <= _cardDetails.maxINNLength) {
|
||||||
_cardDetails.detectCardProvider();
|
_cardDetails.detectCardProvider();
|
||||||
}
|
}
|
||||||
if (numbers.length == 16) {
|
if (numbers.length == 16) {
|
||||||
_currentCardEntryStepController.add(CardEntryStep.exp);
|
_currentCardEntryStepController
|
||||||
|
.add(CardEntryStep.exp);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFieldSubmitted: (_) => _currentCardEntryStepController.add(CardEntryStep.exp),
|
onFieldSubmitted: (_) =>
|
||||||
|
_currentCardEntryStepController
|
||||||
|
.add(CardEntryStep.exp),
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(19),
|
LengthLimitingTextInputFormatter(19),
|
||||||
FilteringTextInputFormatter.allow(RegExp('[0-9 ]')),
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp('[0-9 ]')),
|
||||||
CardNumberInputFormatter(),
|
CardNumberInputFormatter(),
|
||||||
],
|
],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
@ -437,7 +480,8 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
constraints: _currentStep == CardEntryStep.number
|
constraints:
|
||||||
|
_currentStep == CardEntryStep.number
|
||||||
? BoxConstraints.loose(
|
? BoxConstraints.loose(
|
||||||
Size(_expanderWidthExpanded, 0.0),
|
Size(_expanderWidthExpanded, 0.0),
|
||||||
)
|
)
|
||||||
|
@ -454,7 +498,8 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
children: [
|
children: [
|
||||||
// Must manually add hint label because they wont show on mobile with backspace hack
|
// Must manually add hint label because they wont show on mobile with backspace hack
|
||||||
if (_isMobile && _expirationController.text == '\u200b')
|
if (_isMobile &&
|
||||||
|
_expirationController.text == '\u200b')
|
||||||
Text('MM/YY', style: _hintTextSyle),
|
Text('MM/YY', style: _hintTextSyle),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
key: const Key('expiration_field'),
|
key: const Key('expiration_field'),
|
||||||
|
@ -470,24 +515,37 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
? _errorTextStyle
|
? _errorTextStyle
|
||||||
: _normalTextStyle,
|
: _normalTextStyle,
|
||||||
validator: (content) {
|
validator: (content) {
|
||||||
if (content == null || content.isEmpty || _isMobile && content == '\u200b') {
|
if (content == null ||
|
||||||
|
content.isEmpty ||
|
||||||
|
_isMobile && content == '\u200b') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isMobile) {
|
if (_isMobile) {
|
||||||
setState(() => _cardDetails.expirationString = content.replaceAll('\u200b', ''));
|
setState(() =>
|
||||||
|
_cardDetails.expirationString =
|
||||||
|
content.replaceAll('\u200b', ''));
|
||||||
} else {
|
} else {
|
||||||
setState(() => _cardDetails.expirationString = content);
|
setState(() => _cardDetails
|
||||||
|
.expirationString = content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cardDetails.validState == CardDetailsValidState.dateTooEarly) {
|
if (_cardDetails.validState ==
|
||||||
_setValidationState('Your card\'s expiration date is in the past.');
|
CardDetailsValidState.dateTooEarly) {
|
||||||
} else if (_cardDetails.validState == CardDetailsValidState.dateTooLate) {
|
_setValidationState(
|
||||||
_setValidationState('Your card\'s expiration year is invalid.');
|
'Your card\'s expiration date is in the past.');
|
||||||
} else if (_cardDetails.validState == CardDetailsValidState.missingDate) {
|
} else if (_cardDetails.validState ==
|
||||||
_setValidationState('You must include your card\'s expiration date.');
|
CardDetailsValidState.dateTooLate) {
|
||||||
} else if (_cardDetails.validState == CardDetailsValidState.invalidMonth) {
|
_setValidationState(
|
||||||
_setValidationState('Your card\'s expiration month is invalid.');
|
'Your card\'s expiration year is invalid.');
|
||||||
|
} else if (_cardDetails.validState ==
|
||||||
|
CardDetailsValidState.missingDate) {
|
||||||
|
_setValidationState(
|
||||||
|
'You must include your card\'s expiration date.');
|
||||||
|
} else if (_cardDetails.validState ==
|
||||||
|
CardDetailsValidState.invalidMonth) {
|
||||||
|
_setValidationState(
|
||||||
|
'Your card\'s expiration month is invalid.');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
@ -496,18 +554,25 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
if (str.isEmpty) {
|
if (str.isEmpty) {
|
||||||
_backspacePressed();
|
_backspacePressed();
|
||||||
}
|
}
|
||||||
setState(() => _cardDetails.expirationString = str.replaceAll('\u200b', ''));
|
setState(() =>
|
||||||
|
_cardDetails.expirationString =
|
||||||
|
str.replaceAll('\u200b', ''));
|
||||||
} else {
|
} else {
|
||||||
setState(() => _cardDetails.expirationString = str);
|
setState(() =>
|
||||||
|
_cardDetails.expirationString = str);
|
||||||
}
|
}
|
||||||
if (str.length == 5) {
|
if (str.length == 5) {
|
||||||
_currentCardEntryStepController.add(CardEntryStep.cvc);
|
_currentCardEntryStepController
|
||||||
|
.add(CardEntryStep.cvc);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFieldSubmitted: (_) => _currentCardEntryStepController.add(CardEntryStep.cvc),
|
onFieldSubmitted: (_) =>
|
||||||
|
_currentCardEntryStepController
|
||||||
|
.add(CardEntryStep.cvc),
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(5),
|
LengthLimitingTextInputFormatter(5),
|
||||||
FilteringTextInputFormatter.allow(RegExp('[0-9/]')),
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp('[0-9/]')),
|
||||||
CardExpirationFormatter(),
|
CardExpirationFormatter(),
|
||||||
],
|
],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
@ -526,7 +591,8 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
children: [
|
children: [
|
||||||
if (_isMobile && _securityCodeController.text == '\u200b')
|
if (_isMobile &&
|
||||||
|
_securityCodeController.text == '\u200b')
|
||||||
Text(
|
Text(
|
||||||
'CVC',
|
'CVC',
|
||||||
style: _hintTextSyle,
|
style: _hintTextSyle,
|
||||||
|
@ -536,47 +602,67 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
focusNode: securityCodeFocusNode,
|
focusNode: securityCodeFocusNode,
|
||||||
controller: _securityCodeController,
|
controller: _securityCodeController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
style:
|
style: _isRedText([
|
||||||
_isRedText([CardDetailsValidState.invalidCVC, CardDetailsValidState.missingCVC])
|
CardDetailsValidState.invalidCVC,
|
||||||
|
CardDetailsValidState.missingCVC
|
||||||
|
])
|
||||||
? _errorTextStyle
|
? _errorTextStyle
|
||||||
: _normalTextStyle,
|
: _normalTextStyle,
|
||||||
validator: (content) {
|
validator: (content) {
|
||||||
if (content == null || content.isEmpty || _isMobile && content == '\u200b') {
|
if (content == null ||
|
||||||
|
content.isEmpty ||
|
||||||
|
_isMobile && content == '\u200b') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isMobile) {
|
if (_isMobile) {
|
||||||
setState(() => _cardDetails.securityCode = content.replaceAll('\u200b', ''));
|
setState(() => _cardDetails.securityCode =
|
||||||
|
content.replaceAll('\u200b', ''));
|
||||||
} else {
|
} else {
|
||||||
setState(() => _cardDetails.securityCode = content);
|
setState(() =>
|
||||||
|
_cardDetails.securityCode = content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cardDetails.validState == CardDetailsValidState.invalidCVC) {
|
if (_cardDetails.validState ==
|
||||||
_setValidationState('Your card\'s security code is invalid.');
|
CardDetailsValidState.invalidCVC) {
|
||||||
} else if (_cardDetails.validState == CardDetailsValidState.missingCVC) {
|
_setValidationState(
|
||||||
_setValidationState('Your card\'s security code is incomplete.');
|
'Your card\'s security code is invalid.');
|
||||||
|
} else if (_cardDetails.validState ==
|
||||||
|
CardDetailsValidState.missingCVC) {
|
||||||
|
_setValidationState(
|
||||||
|
'Your card\'s security code is incomplete.');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onFieldSubmitted: (_) => _currentCardEntryStepController.add(CardEntryStep.postal),
|
onFieldSubmitted: (_) =>
|
||||||
|
_currentCardEntryStepController
|
||||||
|
.add(CardEntryStep.postal),
|
||||||
onChanged: (str) {
|
onChanged: (str) {
|
||||||
if (_isMobile) {
|
if (_isMobile) {
|
||||||
if (str.isEmpty) {
|
if (str.isEmpty) {
|
||||||
_backspacePressed();
|
_backspacePressed();
|
||||||
}
|
}
|
||||||
setState(() => _cardDetails.expirationString = str.replaceAll('\u200b', ''));
|
setState(() =>
|
||||||
|
_cardDetails.expirationString =
|
||||||
|
str.replaceAll('\u200b', ''));
|
||||||
} else {
|
} else {
|
||||||
setState(() => _cardDetails.expirationString = str);
|
setState(() =>
|
||||||
|
_cardDetails.expirationString = str);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str.length == _cardDetails.provider?.cvcLength) {
|
if (str.length ==
|
||||||
_currentCardEntryStepController.add(CardEntryStep.postal);
|
_cardDetails.provider?.cvcLength) {
|
||||||
|
_currentCardEntryStepController
|
||||||
|
.add(CardEntryStep.postal);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(
|
LengthLimitingTextInputFormatter(
|
||||||
_cardDetails.provider == null ? 4 : _cardDetails.provider!.cvcLength),
|
_cardDetails.provider == null
|
||||||
FilteringTextInputFormatter.allow(RegExp('[0-9]')),
|
? 4
|
||||||
|
: _cardDetails.provider!.cvcLength),
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp('[0-9]')),
|
||||||
],
|
],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
@ -594,7 +680,8 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
children: [
|
children: [
|
||||||
if (_isMobile && _postalCodeController.text == '\u200b')
|
if (_isMobile &&
|
||||||
|
_postalCodeController.text == '\u200b')
|
||||||
Text(
|
Text(
|
||||||
'Postal Code',
|
'Postal Code',
|
||||||
style: _hintTextSyle,
|
style: _hintTextSyle,
|
||||||
|
@ -604,25 +691,35 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
focusNode: postalCodeFocusNode,
|
focusNode: postalCodeFocusNode,
|
||||||
controller: _postalCodeController,
|
controller: _postalCodeController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
style:
|
style: _isRedText([
|
||||||
_isRedText([CardDetailsValidState.invalidZip, CardDetailsValidState.missingZip])
|
CardDetailsValidState.invalidZip,
|
||||||
|
CardDetailsValidState.missingZip
|
||||||
|
])
|
||||||
? _errorTextStyle
|
? _errorTextStyle
|
||||||
: _normalTextStyle,
|
: _normalTextStyle,
|
||||||
validator: (content) {
|
validator: (content) {
|
||||||
if (content == null || content.isEmpty || _isMobile && content == '\u200b') {
|
if (content == null ||
|
||||||
|
content.isEmpty ||
|
||||||
|
_isMobile && content == '\u200b') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isMobile) {
|
if (_isMobile) {
|
||||||
setState(() => _cardDetails.postalCode = content.replaceAll('\u200b', ''));
|
setState(() => _cardDetails.postalCode =
|
||||||
|
content.replaceAll('\u200b', ''));
|
||||||
} else {
|
} else {
|
||||||
setState(() => _cardDetails.postalCode = content);
|
setState(() =>
|
||||||
|
_cardDetails.postalCode = content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cardDetails.validState == CardDetailsValidState.invalidZip) {
|
if (_cardDetails.validState ==
|
||||||
_setValidationState('The postal code you entered is not correct.');
|
CardDetailsValidState.invalidZip) {
|
||||||
} else if (_cardDetails.validState == CardDetailsValidState.missingZip) {
|
_setValidationState(
|
||||||
_setValidationState('You must enter your card\'s postal code.');
|
'The postal code you entered is not correct.');
|
||||||
|
} else if (_cardDetails.validState ==
|
||||||
|
CardDetailsValidState.missingZip) {
|
||||||
|
_setValidationState(
|
||||||
|
'You must enter your card\'s postal code.');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
@ -631,9 +728,11 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
if (str.isEmpty) {
|
if (str.isEmpty) {
|
||||||
_backspacePressed();
|
_backspacePressed();
|
||||||
}
|
}
|
||||||
setState(() => _cardDetails.postalCode = str.replaceAll('\u200b', ''));
|
setState(() => _cardDetails.postalCode =
|
||||||
|
str.replaceAll('\u200b', ''));
|
||||||
} else {
|
} else {
|
||||||
setState(() => _cardDetails.postalCode = str);
|
setState(
|
||||||
|
() => _cardDetails.postalCode = str);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
|
@ -653,8 +752,12 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
),
|
),
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
opacity: _loading && widget.showInternalLoadingWidget ? 1.0 : 0.0,
|
opacity:
|
||||||
child: widget.loadingWidget ?? const CircularProgressIndicator(),
|
_loading && widget.showInternalLoadingWidget
|
||||||
|
? 1.0
|
||||||
|
: 0.0,
|
||||||
|
child: widget.loadingWidget ??
|
||||||
|
const CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -762,14 +865,20 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
_horizontalScrollController.animateTo(0.0, duration: dur, curve: cur);
|
_horizontalScrollController.animateTo(0.0, duration: dur, curve: cur);
|
||||||
break;
|
break;
|
||||||
case CardEntryStep.exp:
|
case CardEntryStep.exp:
|
||||||
_horizontalScrollController.animateTo(_cardFieldWidth / 2, duration: dur, curve: cur);
|
_horizontalScrollController.animateTo(_cardFieldWidth / 2,
|
||||||
|
duration: dur, curve: cur);
|
||||||
break;
|
break;
|
||||||
case CardEntryStep.cvc:
|
case CardEntryStep.cvc:
|
||||||
_horizontalScrollController.animateTo(_cardFieldWidth / 2 + _expirationFieldWidth, duration: dur, curve: cur);
|
_horizontalScrollController.animateTo(
|
||||||
|
_cardFieldWidth / 2 + _expirationFieldWidth,
|
||||||
|
duration: dur,
|
||||||
|
curve: cur);
|
||||||
break;
|
break;
|
||||||
case CardEntryStep.postal:
|
case CardEntryStep.postal:
|
||||||
_horizontalScrollController.animateTo(_cardFieldWidth / 2 + _expirationFieldWidth + _securityFieldWidth,
|
_horizontalScrollController.animateTo(
|
||||||
duration: dur, curve: cur);
|
_cardFieldWidth / 2 + _expirationFieldWidth + _securityFieldWidth,
|
||||||
|
duration: dur,
|
||||||
|
curve: cur);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -842,11 +951,11 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
case CardEntryStep.number:
|
case CardEntryStep.number:
|
||||||
break;
|
break;
|
||||||
case CardEntryStep.exp:
|
case CardEntryStep.exp:
|
||||||
if (_expirationController.text.isNotEmpty) break;
|
if (_expirationController.text.isNotEmpty) return;
|
||||||
case CardEntryStep.cvc:
|
case CardEntryStep.cvc:
|
||||||
if (_securityCodeController.text.isNotEmpty) break;
|
if (_securityCodeController.text.isNotEmpty) return;
|
||||||
case CardEntryStep.postal:
|
case CardEntryStep.postal:
|
||||||
if (_postalCodeController.text.isNotEmpty) break;
|
if (_postalCodeController.text.isNotEmpty) return;
|
||||||
}
|
}
|
||||||
_transitionStepFocus();
|
_transitionStepFocus();
|
||||||
}
|
}
|
||||||
|
@ -893,7 +1002,8 @@ class CardTextFieldState extends State<CardTextField> {
|
||||||
/// to make the card number display cleanly.
|
/// to make the card number display cleanly.
|
||||||
class CardNumberInputFormatter implements TextInputFormatter {
|
class CardNumberInputFormatter implements TextInputFormatter {
|
||||||
@override
|
@override
|
||||||
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
|
TextEditingValue formatEditUpdate(
|
||||||
|
TextEditingValue oldValue, TextEditingValue newValue) {
|
||||||
String cardNum = newValue.text;
|
String cardNum = newValue.text;
|
||||||
if (cardNum.length <= 4) return newValue;
|
if (cardNum.length <= 4) return newValue;
|
||||||
|
|
||||||
|
@ -908,7 +1018,9 @@ class CardNumberInputFormatter implements TextInputFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newValue.copyWith(text: buffer.toString(), selection: TextSelection.collapsed(offset: buffer.length));
|
return newValue.copyWith(
|
||||||
|
text: buffer.toString(),
|
||||||
|
selection: TextSelection.collapsed(offset: buffer.length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -916,7 +1028,8 @@ class CardNumberInputFormatter implements TextInputFormatter {
|
||||||
/// the month and the year for the expiration date.
|
/// the month and the year for the expiration date.
|
||||||
class CardExpirationFormatter implements TextInputFormatter {
|
class CardExpirationFormatter implements TextInputFormatter {
|
||||||
@override
|
@override
|
||||||
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
|
TextEditingValue formatEditUpdate(
|
||||||
|
TextEditingValue oldValue, TextEditingValue newValue) {
|
||||||
String cardExp = newValue.text;
|
String cardExp = newValue.text;
|
||||||
if (cardExp.length == 1) {
|
if (cardExp.length == 1) {
|
||||||
if (cardExp[0] == '0' || cardExp[0] == '1') {
|
if (cardExp[0] == '0' || cardExp[0] == '1') {
|
||||||
|
@ -937,6 +1050,8 @@ class CardExpirationFormatter implements TextInputFormatter {
|
||||||
buffer.write('/');
|
buffer.write('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newValue.copyWith(text: buffer.toString(), selection: TextSelection.collapsed(offset: buffer.length));
|
return newValue.copyWith(
|
||||||
|
text: buffer.toString(),
|
||||||
|
selection: TextSelection.collapsed(offset: buffer.length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ void main() {
|
||||||
|
|
||||||
final input = TestTextInput();
|
final input = TestTextInput();
|
||||||
|
|
||||||
final cardState = tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
final cardState =
|
||||||
|
tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
||||||
|
|
||||||
assertEmptyTextFields(tester, cardState.isWideFormat);
|
assertEmptyTextFields(tester, cardState.isWideFormat);
|
||||||
|
|
||||||
|
@ -47,7 +48,8 @@ void main() {
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(getTextFormField(expirationFieldKey).controller?.text, '');
|
expect(getTextFormField(expirationFieldKey).controller?.text, '');
|
||||||
expect(getTextFormField(cardFieldKey).controller?.text, '4242 4242 4242 424');
|
expect(getTextFormField(cardFieldKey).controller?.text,
|
||||||
|
'4242 4242 4242 424');
|
||||||
expect(cardState.cardNumberFocusNode.hasFocus, true);
|
expect(cardState.cardNumberFocusNode.hasFocus, true);
|
||||||
expect(cardState.expirationFocusNode.hasFocus, false);
|
expect(cardState.expirationFocusNode.hasFocus, false);
|
||||||
// Postal code should now be gone
|
// Postal code should now be gone
|
||||||
|
@ -58,7 +60,8 @@ void main() {
|
||||||
input.enterText("4242424242424242");
|
input.enterText("4242424242424242");
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(getTextFormField(cardFieldKey).controller?.text, '4242 4242 4242 4242');
|
expect(getTextFormField(cardFieldKey).controller?.text,
|
||||||
|
'4242 4242 4242 4242');
|
||||||
expect(cardState.cardNumberFocusNode.hasFocus, false);
|
expect(cardState.cardNumberFocusNode.hasFocus, false);
|
||||||
expect(cardState.expirationFocusNode.hasFocus, true);
|
expect(cardState.expirationFocusNode.hasFocus, true);
|
||||||
// Postal code should move back into view
|
// Postal code should move back into view
|
||||||
|
@ -90,7 +93,10 @@ void main() {
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
final expectedCardDetails = CardDetails(
|
final expectedCardDetails = CardDetails(
|
||||||
cardNumber: '4242 4242 4242 4242', securityCode: '333', expirationString: '10/28', postalCode: '91555');
|
cardNumber: '4242 4242 4242 4242',
|
||||||
|
securityCode: '333',
|
||||||
|
expirationString: '10/28',
|
||||||
|
postalCode: '91555');
|
||||||
// print('${expectedCardDetails.toString()}\n${details?.toString()}');
|
// print('${expectedCardDetails.toString()}\n${details?.toString()}');
|
||||||
expect(details?.hash, expectedCardDetails.hash);
|
expect(details?.hash, expectedCardDetails.hash);
|
||||||
},
|
},
|
||||||
|
@ -110,7 +116,8 @@ void main() {
|
||||||
|
|
||||||
final input = TestTextInput();
|
final input = TestTextInput();
|
||||||
|
|
||||||
final cardState = tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
final cardState =
|
||||||
|
tester.state(find.byType(CardTextField)) as CardTextFieldState;
|
||||||
|
|
||||||
assertEmptyTextFields(tester, cardState.isWideFormat);
|
assertEmptyTextFields(tester, cardState.isWideFormat);
|
||||||
|
|
||||||
|
@ -135,7 +142,8 @@ void main() {
|
||||||
input.enterText('0055');
|
input.enterText('0055');
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text("Your card's expiration month is invalid."), findsOneWidget);
|
expect(
|
||||||
|
find.text("Your card's expiration month is invalid."), findsOneWidget);
|
||||||
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
@ -145,7 +153,8 @@ void main() {
|
||||||
input.enterText('1099');
|
input.enterText('1099');
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text("Your card's expiration year is invalid."), findsOneWidget);
|
expect(
|
||||||
|
find.text("Your card's expiration year is invalid."), findsOneWidget);
|
||||||
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.backspace);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
@ -169,7 +178,8 @@ void main() {
|
||||||
await input.receiveAction(TextInputAction.done);
|
await input.receiveAction(TextInputAction.done);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text("The postal code you entered is not correct."), findsOneWidget);
|
expect(find.text("The postal code you entered is not correct."),
|
||||||
|
findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.byType(CardTextField));
|
await tester.tap(find.byType(CardTextField));
|
||||||
|
|
||||||
|
@ -182,7 +192,10 @@ void main() {
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
final expectedCardDetails = CardDetails(
|
final expectedCardDetails = CardDetails(
|
||||||
cardNumber: '4242 4242 4242 4242', expirationString: '02/28', securityCode: '123', postalCode: '12345');
|
cardNumber: '4242 4242 4242 4242',
|
||||||
|
expirationString: '02/28',
|
||||||
|
securityCode: '123',
|
||||||
|
postalCode: '12345');
|
||||||
|
|
||||||
expect(details?.hash, expectedCardDetails.hash);
|
expect(details?.hash, expectedCardDetails.hash);
|
||||||
});
|
});
|
||||||
|
@ -209,7 +222,8 @@ void assertEmptyTextFields(WidgetTester tester, bool isWideFormat) {
|
||||||
// expect(find.text("Postal Code"), findsNothing);
|
// expect(find.text("Postal Code"), findsNothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> enterTextByKey(WidgetTester tester, {required String key, required String text}) async {
|
Future<void> enterTextByKey(WidgetTester tester,
|
||||||
|
{required String key, required String text}) async {
|
||||||
await tester.enterText(find.byKey(ValueKey(key)), text);
|
await tester.enterText(find.byKey(ValueKey(key)), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user