This commit is contained in:
Nathan Anderson 2023-12-07 15:37:29 -07:00
parent f4b05dbccf
commit abcbf96d08
5 changed files with 656 additions and 712 deletions

View File

@ -1,3 +1,14 @@
## 0.0.9
- Drastically improved usability and performance with flutter web and canvaskit renderer, especially on mobile
- Using streams to more accurately call `widget.onValidCardDetails` when the card details are valid and completed
- Added `cursorColor` customization
- Reworked widget life cycle so that hot reloads work as expected (resizing, focus, etc.).
## 0.0.8
- Updated dart sdk constraints again... oops (>=3.0.0)
## 0.0.7 ## 0.0.7
- Changed pubspec versioning to allow lower SDK constraints (>=2.12.0) - Changed pubspec versioning to allow lower SDK constraints (>=2.12.0)

View File

@ -45,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.18.0"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -135,10 +135,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -180,18 +180,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -206,7 +206,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.0.7" version: "0.0.9"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -219,10 +219,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.6.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -267,10 +267,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4-beta" version: "0.3.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -280,5 +280,5 @@ packages:
source: hosted source: hosted
version: "6.3.0" version: "6.3.0"
sdks: sdks:
dart: ">=3.1.0-185.0.dev <4.0.0" dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.7.0-0" flutter: ">=3.7.0-0"

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
/// Class encapsulating the card's data /// Class encapsulating the card's data
@ -18,11 +20,7 @@ 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( return CardDetails(cardNumber: null, securityCode: null, expirationString: null, postalCode: null);
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.
@ -39,6 +37,7 @@ class CardDetails {
CardDetailsValidState _validState = CardDetailsValidState.blank; CardDetailsValidState _validState = CardDetailsValidState.blank;
int _lastCheckHash = 0; int _lastCheckHash = 0;
CardProvider? provider; CardProvider? provider;
StreamController<CardDetails> onCompleteController = StreamController();
set overrideValidState(CardDetailsValidState state) => _validState = state; set overrideValidState(CardDetailsValidState state) => _validState = state;
@ -51,13 +50,11 @@ class CardDetails {
String get expMonth => isComplete ? expirationString!.split('/').first : ''; String get expMonth => isComplete ? expirationString!.split('/').first : '';
String get expYear => isComplete ? expirationString!.split('/').last : ''; String get expYear => isComplete ? expirationString!.split('/').last : '';
// TODO rename to be more clear
/// 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 => _cardNumber == null bool get cardNumberFilled =>
? false _cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
: (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.
@ -66,6 +63,14 @@ class CardDetails {
return _complete; return _complete;
} }
/// Detects if the card is complete, then broadcasts
/// card details to `onCompleteController`
void broadcastStatus() {
if (isComplete) {
onCompleteController.add(this);
}
}
/// The maximum length of the INN (identifier) /// The maximum length of the INN (identifier)
/// of a card provider. /// of a card provider.
int get maxINNLength => 4; int get maxINNLength => 4;
@ -82,12 +87,9 @@ class CardDetails {
return; return;
} }
_lastCheckHash = currentHash;
if (_cardNumber == null &&
expirationString == null &&
securityCode == null &&
postalCode == null) {
_complete = false; _complete = false;
_lastCheckHash = currentHash;
if (_cardNumber == null && expirationString == null && securityCode == null && postalCode == null) {
_validState = CardDetailsValidState.blank; _validState = CardDetailsValidState.blank;
return; return;
} }
@ -99,63 +101,50 @@ class CardDetails {
) )
.toList(); .toList();
if (!_luhnAlgorithmCheck(nums)) { if (!_luhnAlgorithmCheck(nums)) {
_complete = false;
_validState = CardDetailsValidState.invalidCard; _validState = CardDetailsValidState.invalidCard;
return; return;
} }
if (_cardNumber == null || !cardNumberFilled) { if (_cardNumber == null || !cardNumberFilled) {
_complete = false;
_validState = CardDetailsValidState.missingCard; _validState = CardDetailsValidState.missingCard;
return; return;
} }
if (expirationString == null) { if (expirationString == null) {
_complete = false;
_validState = CardDetailsValidState.missingDate; _validState = CardDetailsValidState.missingDate;
return; return;
} }
final expSplits = expirationString!.split('/'); final expSplits = expirationString!.split('/');
if (expSplits.length != 2 || expSplits.last == '') { if (expSplits.length != 2 || expSplits.last == '') {
_complete = false;
_validState = CardDetailsValidState.missingDate; _validState = CardDetailsValidState.missingDate;
return; return;
} }
final month = int.parse( final month = int.parse(expSplits.first[0] == '0' ? expSplits.first[1] : expSplits.first);
expSplits.first[0] == '0' ? expSplits.first[1] : expSplits.first);
if (month < 1 || month > 12) { if (month < 1 || month > 12) {
_complete = false;
_validState = CardDetailsValidState.invalidMonth; _validState = CardDetailsValidState.invalidMonth;
return; return;
} }
final year = 2000 + int.parse(expSplits.last); final year = 2000 + int.parse(expSplits.last);
final date = DateTime(year, month); final date = DateTime(year, month);
if (date.isBefore(DateTime.now())) { if (date.isBefore(DateTime.now())) {
_complete = false;
_validState = CardDetailsValidState.dateTooEarly; _validState = CardDetailsValidState.dateTooEarly;
return; return;
} else if (date } else if (date.isAfter(DateTime.now().add(const Duration(days: 365 * 50)))) {
.isAfter(DateTime.now().add(const Duration(days: 365 * 50)))) {
_complete = false;
_validState = CardDetailsValidState.dateTooLate; _validState = CardDetailsValidState.dateTooLate;
return; return;
} }
expirationDate = date; expirationDate = date;
if (securityCode == null) { if (securityCode == null) {
_complete = false;
_validState = CardDetailsValidState.missingCVC; _validState = CardDetailsValidState.missingCVC;
return; return;
} }
if (provider != null && securityCode!.length != provider!.cvcLength) { if (provider != null && securityCode!.length != provider!.cvcLength) {
_complete = false;
_validState = CardDetailsValidState.invalidCVC; _validState = CardDetailsValidState.invalidCVC;
return; return;
} }
if (postalCode == null) { if (postalCode == null) {
_complete = false;
_validState = CardDetailsValidState.missingZip; _validState = CardDetailsValidState.missingZip;
return; return;
} }
if (!RegExp(r'^\d{5}(-\d{4})?$').hasMatch(postalCode!)) { if (!RegExp(r'^\d{5}(-\d{4})?$').hasMatch(postalCode!)) {
_complete = false;
_validState = CardDetailsValidState.invalidZip; _validState = CardDetailsValidState.invalidZip;
return; return;
} }
@ -292,11 +281,7 @@ class CardProvider {
int cvcLength; int cvcLength;
CardProvider( CardProvider(
{required this.id, {required this.id, required this.cardLength, required this.cvcLength, this.innValidNums, this.innValidRanges}) {
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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
name: stripe_native_card_field name: stripe_native_card_field
description: A native flutter implementation of the elegant Stripe Card Field. description: A native flutter implementation of the elegant Stripe Card Field.
version: 0.0.7 version: 0.0.9
repository: https://git.fosscat.com/n8r/stripe_native_card_field repository: https://git.fosscat.com/n8r/stripe_native_card_field
environment: environment: