Added dartdoc comments for PUB POINTS!!
This commit is contained in:
		
							parent
							
								
									f9e758fda5
								
							
						
					
					
						commit
						f23805a3a8
					
				
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					Copyright 2023 Nathan Anderson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BSD 3-Clause License
 | 
					BSD 3-Clause License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 | 
					Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
import 'package:flutter/foundation.dart';
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Class encapsulating the card's data
 | 
				
			||||||
 | 
					/// as well as validation of the data.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// `CardDetails.validState == ValidState.ok`
 | 
				
			||||||
 | 
					/// when fields are filled and validated as correct.
 | 
				
			||||||
class CardDetails {
 | 
					class CardDetails {
 | 
				
			||||||
  CardDetails({
 | 
					  CardDetails({
 | 
				
			||||||
    required dynamic cardNumber,
 | 
					    required dynamic cardNumber,
 | 
				
			||||||
@ -11,10 +16,13 @@ class CardDetails {
 | 
				
			|||||||
    checkIsValid();
 | 
					    checkIsValid();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Sets every field to null, a default
 | 
				
			||||||
 | 
					  /// `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.
 | 
				
			||||||
  String? get cardNumber => _cardNumber?.replaceAll(' ', '');
 | 
					  String? get cardNumber => _cardNumber?.replaceAll(' ', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set cardNumber(String? num) => _cardNumber = num;
 | 
					  set cardNumber(String? num) => _cardNumber = num;
 | 
				
			||||||
@ -29,22 +37,36 @@ class CardDetails {
 | 
				
			|||||||
  int _lastCheckHash = 0;
 | 
					  int _lastCheckHash = 0;
 | 
				
			||||||
  CardProvider? provider;
 | 
					  CardProvider? provider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Checks the validity of the `CardDetails` and returns the result.
 | 
				
			||||||
  ValidState get validState {
 | 
					  ValidState get validState {
 | 
				
			||||||
    checkIsValid();
 | 
					    checkIsValid();
 | 
				
			||||||
    return _validState;
 | 
					    return _validState;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // TODO rename to be more clear
 | 
				
			||||||
 | 
					  /// Returns true if `_cardNumber` is null, or
 | 
				
			||||||
 | 
					  /// if the _cardNumber matches the detected `provider`'s
 | 
				
			||||||
 | 
					  /// card lenght, defaulting to 16.
 | 
				
			||||||
  bool get cardNumberFilled =>
 | 
					  bool get cardNumberFilled =>
 | 
				
			||||||
      _cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
 | 
					      _cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Returns true if all details are complete and valid
 | 
				
			||||||
 | 
					  /// otherwise, return false.
 | 
				
			||||||
  bool get isComplete {
 | 
					  bool get isComplete {
 | 
				
			||||||
    checkIsValid();
 | 
					    checkIsValid();
 | 
				
			||||||
    return _complete;
 | 
					    return _complete;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int get minInnLength => 1;
 | 
					  /// The maximum length of the INN (identifier)
 | 
				
			||||||
 | 
					  /// of a card provider. 
 | 
				
			||||||
  int get maxINNLength => 4;
 | 
					  int get maxINNLength => 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Validates each field of the `CardDetails` object in entry order,
 | 
				
			||||||
 | 
					  /// namely _cardNumber -> expirationString -> securityCode -> postalCode
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// If all fields are filled out and valid, `CardDetails.isComplete == true`
 | 
				
			||||||
 | 
					  /// and `CardDetails.validState == ValidState.ok`.
 | 
				
			||||||
  void checkIsValid() {
 | 
					  void checkIsValid() {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      int currentHash = hash;
 | 
					      int currentHash = hash;
 | 
				
			||||||
@ -65,7 +87,7 @@ class CardDetails {
 | 
				
			|||||||
            (i) => int.parse(i),
 | 
					            (i) => int.parse(i),
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
          .toList();
 | 
					          .toList();
 | 
				
			||||||
      if (!luhnAlgorithmCheck(nums)) {
 | 
					      if (!_luhnAlgorithmCheck(nums)) {
 | 
				
			||||||
        _complete = false;
 | 
					        _complete = false;
 | 
				
			||||||
        _validState = ValidState.invalidCard;
 | 
					        _validState = ValidState.invalidCard;
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@ -130,16 +152,21 @@ class CardDetails {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Provides a hash of the CardDetails object
 | 
				
			||||||
 | 
					  /// Hashes `_cardNumber`, `expirationString`,
 | 
				
			||||||
 | 
					  /// `securityCode`, and `postalCode`.
 | 
				
			||||||
  int get hash {
 | 
					  int get hash {
 | 
				
			||||||
    return Object.hash(_cardNumber, expirationString, securityCode, postalCode);
 | 
					    return Object.hash(_cardNumber, expirationString, securityCode, postalCode);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Iterates over the list `_providers`, detecting which
 | 
				
			||||||
 | 
					  /// provider the current `_cardNumber` falls under.
 | 
				
			||||||
  void detectCardProvider() {
 | 
					  void detectCardProvider() {
 | 
				
			||||||
    bool found = false;
 | 
					    bool found = false;
 | 
				
			||||||
    if (_cardNumber == null) {
 | 
					    if (_cardNumber == null) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (var cardPvd in providers) {
 | 
					    for (var cardPvd in _providers) {
 | 
				
			||||||
      if (cardPvd.innValidNums != null) {
 | 
					      if (cardPvd.innValidNums != null) {
 | 
				
			||||||
        // trim card number to correct length
 | 
					        // trim card number to correct length
 | 
				
			||||||
        String trimmedNum = _cardNumber!;
 | 
					        String trimmedNum = _cardNumber!;
 | 
				
			||||||
@ -180,115 +207,16 @@ class CardDetails {
 | 
				
			|||||||
  String toString() {
 | 
					  String toString() {
 | 
				
			||||||
    return 'Number: "$_cardNumber" - Exp: "$expirationString" CVC: $securityCode Zip: "$postalCode"';
 | 
					    return 'Number: "$_cardNumber" - Exp: "$expirationString" CVC: $securityCode Zip: "$postalCode"';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum ValidState {
 | 
					  /// https://en.wikipedia.org/wiki/Luhn_algorithm
 | 
				
			||||||
  ok,
 | 
					  /// The Luhn algorithm is used in industry to check
 | 
				
			||||||
  error,
 | 
					  /// for valid credit / debit card numbers
 | 
				
			||||||
  blank,
 | 
					  ///
 | 
				
			||||||
  missingCard,
 | 
					  /// The algorithm adds together all the numbers, every
 | 
				
			||||||
  invalidCard,
 | 
					  /// other number is doubled, then the sum is checked to
 | 
				
			||||||
  missingDate,
 | 
					  /// see if it is a multiple of 10.
 | 
				
			||||||
  invalidMonth,
 | 
					  /// https://en.wikipedia.org/wiki/Luhn_algorithm
 | 
				
			||||||
  dateTooEarly,
 | 
					  bool _luhnAlgorithmCheck(List<int> digits) {
 | 
				
			||||||
  dateTooLate,
 | 
					 | 
				
			||||||
  missingCVC,
 | 
					 | 
				
			||||||
  invalidCVC,
 | 
					 | 
				
			||||||
  missingZip,
 | 
					 | 
				
			||||||
  invalidZip,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum CardProviderID {
 | 
					 | 
				
			||||||
  americanExpress,
 | 
					 | 
				
			||||||
  dinersClub,
 | 
					 | 
				
			||||||
  discoverCard,
 | 
					 | 
				
			||||||
  mastercard,
 | 
					 | 
				
			||||||
  jcb,
 | 
					 | 
				
			||||||
  visa,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CardProvider {
 | 
					 | 
				
			||||||
  CardProviderID id;
 | 
					 | 
				
			||||||
  List<int>? innValidNums;
 | 
					 | 
				
			||||||
  List<Range>? innValidRanges;
 | 
					 | 
				
			||||||
  int cardLength;
 | 
					 | 
				
			||||||
  int cvcLength;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
      {required this.id, required this.cardLength, required this.cvcLength, this.innValidNums, this.innValidRanges}) {
 | 
					 | 
				
			||||||
    // Must provide one or the other
 | 
					 | 
				
			||||||
    assert(innValidNums != null || innValidRanges != null);
 | 
					 | 
				
			||||||
    // Do not provide empty list of valid nums
 | 
					 | 
				
			||||||
    assert(innValidNums == null || innValidNums!.isNotEmpty);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  String toString() {
 | 
					 | 
				
			||||||
    return id.toString();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Range {
 | 
					 | 
				
			||||||
  int high;
 | 
					 | 
				
			||||||
  int low;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Range({required this.low, required this.high}) {
 | 
					 | 
				
			||||||
    assert(low <= high);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool isWithin(int val) {
 | 
					 | 
				
			||||||
    return low <= val && val <= high;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
List<CardProvider> providers = [
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
    id: CardProviderID.americanExpress,
 | 
					 | 
				
			||||||
    cardLength: 15,
 | 
					 | 
				
			||||||
    cvcLength: 4,
 | 
					 | 
				
			||||||
    innValidNums: [34, 37],
 | 
					 | 
				
			||||||
  ),
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
    id: CardProviderID.dinersClub,
 | 
					 | 
				
			||||||
    cardLength: 16,
 | 
					 | 
				
			||||||
    cvcLength: 3,
 | 
					 | 
				
			||||||
    innValidNums: [30, 36, 38, 39],
 | 
					 | 
				
			||||||
  ),
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
    id: CardProviderID.discoverCard,
 | 
					 | 
				
			||||||
    cardLength: 16,
 | 
					 | 
				
			||||||
    cvcLength: 3,
 | 
					 | 
				
			||||||
    innValidNums: [60, 65],
 | 
					 | 
				
			||||||
    innValidRanges: [Range(low: 644, high: 649)],
 | 
					 | 
				
			||||||
  ),
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
    id: CardProviderID.jcb,
 | 
					 | 
				
			||||||
    cardLength: 16,
 | 
					 | 
				
			||||||
    cvcLength: 3,
 | 
					 | 
				
			||||||
    innValidNums: [35],
 | 
					 | 
				
			||||||
  ),
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
    id: CardProviderID.mastercard,
 | 
					 | 
				
			||||||
    cardLength: 16,
 | 
					 | 
				
			||||||
    cvcLength: 3,
 | 
					 | 
				
			||||||
    innValidRanges: [Range(low: 22, high: 27), Range(low: 51, high: 55)],
 | 
					 | 
				
			||||||
  ),
 | 
					 | 
				
			||||||
  CardProvider(
 | 
					 | 
				
			||||||
    id: CardProviderID.visa,
 | 
					 | 
				
			||||||
    cardLength: 16,
 | 
					 | 
				
			||||||
    cvcLength: 3,
 | 
					 | 
				
			||||||
    innValidNums: [4],
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://en.wikipedia.org/wiki/Luhn_algorithm
 | 
					 | 
				
			||||||
// The Luhn algorithm is used in industry to check
 | 
					 | 
				
			||||||
// for valid credit / debit card numbers
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// The algorithm adds together all the numbers, every
 | 
					 | 
				
			||||||
// other number is doubled, then the sum is checked to
 | 
					 | 
				
			||||||
// see if it is a multiple of 10.
 | 
					 | 
				
			||||||
bool luhnAlgorithmCheck(List<int> digits) {
 | 
					 | 
				
			||||||
    int sum = 0;
 | 
					    int sum = 0;
 | 
				
			||||||
    bool isSecond = false;
 | 
					    bool isSecond = false;
 | 
				
			||||||
    for (int i = digits.length - 1; i >= 0; i--) {
 | 
					    for (int i = digits.length - 1; i >= 0; i--) {
 | 
				
			||||||
@ -306,3 +234,118 @@ bool luhnAlgorithmCheck(List<int> digits) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return (sum % 10) == 0;
 | 
					    return (sum % 10) == 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Enum of validation states a `CardDetails` object can have.
 | 
				
			||||||
 | 
					enum ValidState {
 | 
				
			||||||
 | 
					  ok,
 | 
				
			||||||
 | 
					  error,
 | 
				
			||||||
 | 
					  blank,
 | 
				
			||||||
 | 
					  missingCard,
 | 
				
			||||||
 | 
					  invalidCard,
 | 
				
			||||||
 | 
					  missingDate,
 | 
				
			||||||
 | 
					  invalidMonth,
 | 
				
			||||||
 | 
					  dateTooEarly,
 | 
				
			||||||
 | 
					  dateTooLate,
 | 
				
			||||||
 | 
					  missingCVC,
 | 
				
			||||||
 | 
					  invalidCVC,
 | 
				
			||||||
 | 
					  missingZip,
 | 
				
			||||||
 | 
					  invalidZip,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Enum of supported U.S. Card Providers
 | 
				
			||||||
 | 
					enum CardProviderID {
 | 
				
			||||||
 | 
					  americanExpress,
 | 
				
			||||||
 | 
					  dinersClub,
 | 
				
			||||||
 | 
					  discoverCard,
 | 
				
			||||||
 | 
					  mastercard,
 | 
				
			||||||
 | 
					  jcb,
 | 
				
			||||||
 | 
					  visa,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Encapsulates criteria for Card Providers in the U.S.
 | 
				
			||||||
 | 
					/// Used by `CardDetails.detectCardProvider()` to determine
 | 
				
			||||||
 | 
					/// a card's Provider. 
 | 
				
			||||||
 | 
					class CardProvider {
 | 
				
			||||||
 | 
					  CardProviderID id;
 | 
				
			||||||
 | 
					  List<int>? innValidNums;
 | 
				
			||||||
 | 
					  List<_Range>? innValidRanges;
 | 
				
			||||||
 | 
					  int cardLength;
 | 
				
			||||||
 | 
					  int cvcLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					      {required this.id, required this.cardLength, required this.cvcLength, this.innValidNums, this.innValidRanges}) {
 | 
				
			||||||
 | 
					    // Must provide one or the other
 | 
				
			||||||
 | 
					    assert(innValidNums != null || innValidRanges != null);
 | 
				
			||||||
 | 
					    // Do not provide empty list of valid nums
 | 
				
			||||||
 | 
					    assert(innValidNums == null || innValidNums!.isNotEmpty);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String toString() {
 | 
				
			||||||
 | 
					    return id.toString();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Object for `CardProvider` to determine valid number ranges.
 | 
				
			||||||
 | 
					/// A loose wrapper on a tuple, that provides assertion of
 | 
				
			||||||
 | 
					/// valid inputs and the `isWithin()` helper function. 
 | 
				
			||||||
 | 
					class _Range {
 | 
				
			||||||
 | 
					  int high;
 | 
				
			||||||
 | 
					  int low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _Range({required this.low, required this.high}) {
 | 
				
			||||||
 | 
					    assert(low <= high);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Returns bool whether or not `val` is between `low` and `high`.
 | 
				
			||||||
 | 
					  /// The range includes the `val`, so
 | 
				
			||||||
 | 
					  /// ```dart
 | 
				
			||||||
 | 
					  /// Range(low: 1, high: 3).isWithin(3);
 | 
				
			||||||
 | 
					  /// ```
 | 
				
			||||||
 | 
					  /// would return true.
 | 
				
			||||||
 | 
					  bool isWithin(int val) {
 | 
				
			||||||
 | 
					    return low <= val && val <= high;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// List of CardProviders for US-based Credit / Debit Cards.
 | 
				
			||||||
 | 
					List<CardProvider> _providers = [
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					    id: CardProviderID.americanExpress,
 | 
				
			||||||
 | 
					    cardLength: 15,
 | 
				
			||||||
 | 
					    cvcLength: 4,
 | 
				
			||||||
 | 
					    innValidNums: [34, 37],
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					    id: CardProviderID.dinersClub,
 | 
				
			||||||
 | 
					    cardLength: 16,
 | 
				
			||||||
 | 
					    cvcLength: 3,
 | 
				
			||||||
 | 
					    innValidNums: [30, 36, 38, 39],
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					    id: CardProviderID.discoverCard,
 | 
				
			||||||
 | 
					    cardLength: 16,
 | 
				
			||||||
 | 
					    cvcLength: 3,
 | 
				
			||||||
 | 
					    innValidNums: [60, 65],
 | 
				
			||||||
 | 
					    innValidRanges: [_Range(low: 644, high: 649)],
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					    id: CardProviderID.jcb,
 | 
				
			||||||
 | 
					    cardLength: 16,
 | 
				
			||||||
 | 
					    cvcLength: 3,
 | 
				
			||||||
 | 
					    innValidNums: [35],
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					    id: CardProviderID.mastercard,
 | 
				
			||||||
 | 
					    cardLength: 16,
 | 
				
			||||||
 | 
					    cvcLength: 3,
 | 
				
			||||||
 | 
					    innValidRanges: [_Range(low: 22, high: 27), _Range(low: 51, high: 55)],
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  CardProvider(
 | 
				
			||||||
 | 
					    id: CardProviderID.visa,
 | 
				
			||||||
 | 
					    cardLength: 16,
 | 
				
			||||||
 | 
					    cvcLength: 3,
 | 
				
			||||||
 | 
					    innValidNums: [4],
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,10 @@ import 'card_details.dart';
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_svg/flutter_svg.dart';
 | 
					import 'package:flutter_svg/flutter_svg.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Widget that provides the various supported card provider's
 | 
				
			||||||
 | 
					/// icons, as well as a default and error card icon.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// To see a list of supported card providers, see `CardDetails.provider`.
 | 
				
			||||||
class CardProviderIcon extends StatefulWidget {
 | 
					class CardProviderIcon extends StatefulWidget {
 | 
				
			||||||
  const CardProviderIcon({required this.cardDetails, super.key});
 | 
					  const CardProviderIcon({required this.cardDetails, super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,6 +81,7 @@ class _CardProviderIconState extends State<CardProviderIcon> {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Helper function to create the SVG icons provided a `CardProviderID`.
 | 
				
			||||||
  Widget createCardSvg(CardProviderID id) {
 | 
					  Widget createCardSvg(CardProviderID id) {
 | 
				
			||||||
    return SvgPicture.string(
 | 
					    return SvgPicture.string(
 | 
				
			||||||
      key: Key('${id.name}-card'),
 | 
					      key: Key('${id.name}-card'),
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,18 @@ import 'card_provider_icon.dart';
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Enum to track each step of the card detail
 | 
				
			||||||
 | 
					/// entry process.
 | 
				
			||||||
enum CardEntryStep { number, exp, cvc, postal }
 | 
					enum CardEntryStep { number, exp, cvc, postal }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A uniform text field for entering card details, based
 | 
				
			||||||
 | 
					/// on the behavior of Stripe's various html elements.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Required `width` and `onCardDetailsComplete`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// If the provided `width < 450.0`, the `CardTextField`
 | 
				
			||||||
 | 
					/// will scroll its content horizontally with the cursor
 | 
				
			||||||
 | 
					/// to compensate.
 | 
				
			||||||
class CardTextField extends StatefulWidget {
 | 
					class CardTextField extends StatefulWidget {
 | 
				
			||||||
  const CardTextField(
 | 
					  const CardTextField(
 | 
				
			||||||
      {Key? key,
 | 
					      {Key? key,
 | 
				
			||||||
@ -23,6 +33,7 @@ class CardTextField extends StatefulWidget {
 | 
				
			|||||||
  final BoxDecoration? boxDecoration; // TODO unapplied style
 | 
					  final BoxDecoration? boxDecoration; // TODO unapplied style
 | 
				
			||||||
  final BoxDecoration? errorBoxDecoration; // TODO unapplied style 
 | 
					  final BoxDecoration? errorBoxDecoration; // TODO unapplied style 
 | 
				
			||||||
  final double width;
 | 
					  final double width;
 | 
				
			||||||
 | 
					  /// Callback that returns the completed CardDetails object
 | 
				
			||||||
  final void Function(CardDetails) onCardDetailsComplete;
 | 
					  final void Function(CardDetails) onCardDetailsComplete;
 | 
				
			||||||
  final double? height;
 | 
					  final double? height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,6 +41,9 @@ class CardTextField extends StatefulWidget {
 | 
				
			|||||||
  State<CardTextField> createState() => CardTextFieldState();
 | 
					  State<CardTextField> createState() => CardTextFieldState();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// State Widget for CardTextField
 | 
				
			||||||
 | 
					/// Should not be used directly, create a
 | 
				
			||||||
 | 
					/// `CardTextField()` instead.
 | 
				
			||||||
@visibleForTesting
 | 
					@visibleForTesting
 | 
				
			||||||
class CardTextFieldState extends State<CardTextField> {
 | 
					class CardTextFieldState extends State<CardTextField> {
 | 
				
			||||||
  late TextEditingController _cardNumberController;
 | 
					  late TextEditingController _cardNumberController;
 | 
				
			||||||
@ -361,10 +375,14 @@ class CardTextFieldState extends State<CardTextField> {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Provided a list of `ValidState`, returns whether
 | 
				
			||||||
 | 
					  /// make the text field red
 | 
				
			||||||
  bool _isRedText(List<ValidState> args) {
 | 
					  bool _isRedText(List<ValidState> args) {
 | 
				
			||||||
    return _showBorderError && args.contains(_cardDetails.validState);
 | 
					    return _showBorderError && args.contains(_cardDetails.validState);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Helper function to change the `_showBorderError` and
 | 
				
			||||||
 | 
					  /// `_validationErrorText`.
 | 
				
			||||||
  void _setValidationState(String? text) {
 | 
					  void _setValidationState(String? text) {
 | 
				
			||||||
    setState(() {
 | 
					    setState(() {
 | 
				
			||||||
      _validationErrorText = text;
 | 
					      _validationErrorText = text;
 | 
				
			||||||
@ -372,6 +390,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Calls `validate()` on the form state and resets
 | 
				
			||||||
 | 
					  /// the validation state
 | 
				
			||||||
  void _validateFields() {
 | 
					  void _validateFields() {
 | 
				
			||||||
    _validationErrorText = null;
 | 
					    _validationErrorText = null;
 | 
				
			||||||
    _formFieldKey.currentState!.validate();
 | 
					    _formFieldKey.currentState!.validate();
 | 
				
			||||||
@ -382,6 +402,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Used when `_isWideFormat == false`, scrolls
 | 
				
			||||||
 | 
					  /// the `_horizontalScrollController` to a given offset 
 | 
				
			||||||
  void _scrollRow(CardEntryStep step) {
 | 
					  void _scrollRow(CardEntryStep step) {
 | 
				
			||||||
    const dur = Duration(milliseconds: 150);
 | 
					    const dur = Duration(milliseconds: 150);
 | 
				
			||||||
    const cur = Curves.easeOut;
 | 
					    const cur = Curves.easeOut;
 | 
				
			||||||
@ -402,6 +424,9 @@ class CardTextFieldState extends State<CardTextField> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Function that is listening to the `_currentCardEntryStepController`
 | 
				
			||||||
 | 
					  /// StreamController. Manages validation and tracking of the current step
 | 
				
			||||||
 | 
					  /// as well as scrolling the text fields.
 | 
				
			||||||
  void _onStepChange(CardEntryStep step) {
 | 
					  void _onStepChange(CardEntryStep step) {
 | 
				
			||||||
    if (_currentStep.index < step.index) {
 | 
					    if (_currentStep.index < step.index) {
 | 
				
			||||||
      _validateFields();
 | 
					      _validateFields();
 | 
				
			||||||
@ -431,6 +456,11 @@ class CardTextFieldState extends State<CardTextField> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Function that is listening to the keyboard events.
 | 
				
			||||||
 | 
					  ///
 | 
				
			||||||
 | 
					  /// This provides the functionality of hitting backspace
 | 
				
			||||||
 | 
					  /// and the focus changing between fields when the current
 | 
				
			||||||
 | 
					  /// entry step is empty.
 | 
				
			||||||
  void _backspaceTransitionListener(RawKeyEvent value) {
 | 
					  void _backspaceTransitionListener(RawKeyEvent value) {
 | 
				
			||||||
    if (!value.isKeyPressed(LogicalKeyboardKey.backspace)) {
 | 
					    if (!value.isKeyPressed(LogicalKeyboardKey.backspace)) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@ -461,6 +491,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Formatter that adds the appropriate space ' ' characters
 | 
				
			||||||
 | 
					/// 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) {
 | 
				
			||||||
@ -482,6 +514,8 @@ class CardNumberInputFormatter implements TextInputFormatter {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Formatter that adds a backslash '/' character in between
 | 
				
			||||||
 | 
					/// 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) {
 | 
				
			||||||
@ -494,11 +528,6 @@ class CardExpirationFormatter implements TextInputFormatter {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (cardExp.length == 2 && oldValue.text.length == 3) return newValue;
 | 
					    if (cardExp.length == 2 && oldValue.text.length == 3) return newValue;
 | 
				
			||||||
    // Auto delete the slash on backspace
 | 
					 | 
				
			||||||
    // if (cardExp.length == 3 && oldValue.text.length == 4 && cardExp[2] == '/') {
 | 
					 | 
				
			||||||
    //   return newValue.copyWith(
 | 
					 | 
				
			||||||
    //       text: cardExp.substring(0, 2), selection: TextSelection.collapsed(offset: cardExp.length - 1));
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cardExp = cardExp.replaceAll('/', '');
 | 
					    cardExp = cardExp.replaceAll('/', '');
 | 
				
			||||||
    StringBuffer buffer = StringBuffer();
 | 
					    StringBuffer buffer = StringBuffer();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user