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
 | 
			
		||||
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
/// 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 {
 | 
			
		||||
  CardDetails({
 | 
			
		||||
    required dynamic cardNumber,
 | 
			
		||||
@ -11,10 +16,13 @@ class CardDetails {
 | 
			
		||||
    checkIsValid();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Sets every field to null, a default
 | 
			
		||||
  /// `CardDetails` when nothing has been entered.
 | 
			
		||||
  factory CardDetails.blank() {
 | 
			
		||||
    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(' ', '');
 | 
			
		||||
 | 
			
		||||
  set cardNumber(String? num) => _cardNumber = num;
 | 
			
		||||
@ -29,22 +37,36 @@ class CardDetails {
 | 
			
		||||
  int _lastCheckHash = 0;
 | 
			
		||||
  CardProvider? provider;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /// Checks the validity of the `CardDetails` and returns the result.
 | 
			
		||||
  ValidState get validState {
 | 
			
		||||
    checkIsValid();
 | 
			
		||||
    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 =>
 | 
			
		||||
      _cardNumber == null ? false : (provider?.cardLength ?? 16) == _cardNumber!.replaceAll(' ', '').length;
 | 
			
		||||
 | 
			
		||||
  /// Returns true if all details are complete and valid
 | 
			
		||||
  /// otherwise, return false.
 | 
			
		||||
  bool get isComplete {
 | 
			
		||||
    checkIsValid();
 | 
			
		||||
    return _complete;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int get minInnLength => 1;
 | 
			
		||||
  /// The maximum length of the INN (identifier)
 | 
			
		||||
  /// of a card provider. 
 | 
			
		||||
  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() {
 | 
			
		||||
    try {
 | 
			
		||||
      int currentHash = hash;
 | 
			
		||||
@ -65,7 +87,7 @@ class CardDetails {
 | 
			
		||||
            (i) => int.parse(i),
 | 
			
		||||
          )
 | 
			
		||||
          .toList();
 | 
			
		||||
      if (!luhnAlgorithmCheck(nums)) {
 | 
			
		||||
      if (!_luhnAlgorithmCheck(nums)) {
 | 
			
		||||
        _complete = false;
 | 
			
		||||
        _validState = ValidState.invalidCard;
 | 
			
		||||
        return;
 | 
			
		||||
@ -130,16 +152,21 @@ class CardDetails {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Provides a hash of the CardDetails object
 | 
			
		||||
  /// Hashes `_cardNumber`, `expirationString`,
 | 
			
		||||
  /// `securityCode`, and `postalCode`.
 | 
			
		||||
  int get hash {
 | 
			
		||||
    return Object.hash(_cardNumber, expirationString, securityCode, postalCode);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Iterates over the list `_providers`, detecting which
 | 
			
		||||
  /// provider the current `_cardNumber` falls under.
 | 
			
		||||
  void detectCardProvider() {
 | 
			
		||||
    bool found = false;
 | 
			
		||||
    if (_cardNumber == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    for (var cardPvd in providers) {
 | 
			
		||||
    for (var cardPvd in _providers) {
 | 
			
		||||
      if (cardPvd.innValidNums != null) {
 | 
			
		||||
        // trim card number to correct length
 | 
			
		||||
        String trimmedNum = _cardNumber!;
 | 
			
		||||
@ -180,8 +207,36 @@ class CardDetails {
 | 
			
		||||
  String toString() {
 | 
			
		||||
    return 'Number: "$_cardNumber" - Exp: "$expirationString" CVC: $securityCode Zip: "$postalCode"';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// 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.
 | 
			
		||||
  /// https://en.wikipedia.org/wiki/Luhn_algorithm
 | 
			
		||||
  bool _luhnAlgorithmCheck(List<int> digits) {
 | 
			
		||||
    int sum = 0;
 | 
			
		||||
    bool isSecond = false;
 | 
			
		||||
    for (int i = digits.length - 1; i >= 0; i--) {
 | 
			
		||||
      int d = digits[i];
 | 
			
		||||
      if (isSecond) {
 | 
			
		||||
        d *= 2;
 | 
			
		||||
 | 
			
		||||
        if (d > 9) {
 | 
			
		||||
          d -= 9;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      sum += d;
 | 
			
		||||
      isSecond = !isSecond;
 | 
			
		||||
    }
 | 
			
		||||
    return (sum % 10) == 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Enum of validation states a `CardDetails` object can have.
 | 
			
		||||
enum ValidState {
 | 
			
		||||
  ok,
 | 
			
		||||
  error,
 | 
			
		||||
@ -198,6 +253,7 @@ enum ValidState {
 | 
			
		||||
  invalidZip,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Enum of supported U.S. Card Providers
 | 
			
		||||
enum CardProviderID {
 | 
			
		||||
  americanExpress,
 | 
			
		||||
  dinersClub,
 | 
			
		||||
@ -207,10 +263,13 @@ enum CardProviderID {
 | 
			
		||||
  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;
 | 
			
		||||
  List<_Range>? innValidRanges;
 | 
			
		||||
  int cardLength;
 | 
			
		||||
  int cvcLength;
 | 
			
		||||
 | 
			
		||||
@ -228,20 +287,30 @@ class CardProvider {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Range {
 | 
			
		||||
/// 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}) {
 | 
			
		||||
  _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<CardProvider> providers = [
 | 
			
		||||
/// List of CardProviders for US-based Credit / Debit Cards.
 | 
			
		||||
List<CardProvider> _providers = [
 | 
			
		||||
  CardProvider(
 | 
			
		||||
    id: CardProviderID.americanExpress,
 | 
			
		||||
    cardLength: 15,
 | 
			
		||||
@ -259,7 +328,7 @@ List<CardProvider> providers = [
 | 
			
		||||
    cardLength: 16,
 | 
			
		||||
    cvcLength: 3,
 | 
			
		||||
    innValidNums: [60, 65],
 | 
			
		||||
    innValidRanges: [Range(low: 644, high: 649)],
 | 
			
		||||
    innValidRanges: [_Range(low: 644, high: 649)],
 | 
			
		||||
  ),
 | 
			
		||||
  CardProvider(
 | 
			
		||||
    id: CardProviderID.jcb,
 | 
			
		||||
@ -271,7 +340,7 @@ List<CardProvider> providers = [
 | 
			
		||||
    id: CardProviderID.mastercard,
 | 
			
		||||
    cardLength: 16,
 | 
			
		||||
    cvcLength: 3,
 | 
			
		||||
    innValidRanges: [Range(low: 22, high: 27), Range(low: 51, high: 55)],
 | 
			
		||||
    innValidRanges: [_Range(low: 22, high: 27), _Range(low: 51, high: 55)],
 | 
			
		||||
  ),
 | 
			
		||||
  CardProvider(
 | 
			
		||||
    id: CardProviderID.visa,
 | 
			
		||||
@ -280,29 +349,3 @@ List<CardProvider> providers = [
 | 
			
		||||
    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;
 | 
			
		||||
  bool isSecond = false;
 | 
			
		||||
  for (int i = digits.length - 1; i >= 0; i--) {
 | 
			
		||||
    int d = digits[i];
 | 
			
		||||
    if (isSecond) {
 | 
			
		||||
      d *= 2;
 | 
			
		||||
 | 
			
		||||
      if (d > 9) {
 | 
			
		||||
        d -= 9;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sum += d;
 | 
			
		||||
    isSecond = !isSecond;
 | 
			
		||||
  }
 | 
			
		||||
  return (sum % 10) == 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,10 @@ import 'card_details.dart';
 | 
			
		||||
import 'package:flutter/material.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 {
 | 
			
		||||
  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) {
 | 
			
		||||
    return SvgPicture.string(
 | 
			
		||||
      key: Key('${id.name}-card'),
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,18 @@ import 'card_provider_icon.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
 | 
			
		||||
/// Enum to track each step of the card detail
 | 
			
		||||
/// entry process.
 | 
			
		||||
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 {
 | 
			
		||||
  const CardTextField(
 | 
			
		||||
      {Key? key,
 | 
			
		||||
@ -21,8 +31,9 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
  final InputDecoration? inputDecoration; // TODO unapplied style
 | 
			
		||||
  final BoxDecoration? boxDecoration; // TODO unapplied style
 | 
			
		||||
  final BoxDecoration? errorBoxDecoration; // TODO unapplied style
 | 
			
		||||
  final BoxDecoration? errorBoxDecoration; // TODO unapplied style 
 | 
			
		||||
  final double width;
 | 
			
		||||
  /// Callback that returns the completed CardDetails object
 | 
			
		||||
  final void Function(CardDetails) onCardDetailsComplete;
 | 
			
		||||
  final double? height;
 | 
			
		||||
 | 
			
		||||
@ -30,6 +41,9 @@ class CardTextField extends StatefulWidget {
 | 
			
		||||
  State<CardTextField> createState() => CardTextFieldState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// State Widget for CardTextField
 | 
			
		||||
/// Should not be used directly, create a
 | 
			
		||||
/// `CardTextField()` instead.
 | 
			
		||||
@visibleForTesting
 | 
			
		||||
class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
  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) {
 | 
			
		||||
    return _showBorderError && args.contains(_cardDetails.validState);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Helper function to change the `_showBorderError` and
 | 
			
		||||
  /// `_validationErrorText`.
 | 
			
		||||
  void _setValidationState(String? text) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _validationErrorText = text;
 | 
			
		||||
@ -372,6 +390,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Calls `validate()` on the form state and resets
 | 
			
		||||
  /// the validation state
 | 
			
		||||
  void _validateFields() {
 | 
			
		||||
    _validationErrorText = null;
 | 
			
		||||
    _formFieldKey.currentState!.validate();
 | 
			
		||||
@ -382,6 +402,8 @@ class CardTextFieldState extends State<CardTextField> {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Used when `_isWideFormat == false`, scrolls
 | 
			
		||||
  /// the `_horizontalScrollController` to a given offset 
 | 
			
		||||
  void _scrollRow(CardEntryStep step) {
 | 
			
		||||
    const dur = Duration(milliseconds: 150);
 | 
			
		||||
    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) {
 | 
			
		||||
    if (_currentStep.index < step.index) {
 | 
			
		||||
      _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) {
 | 
			
		||||
    if (!value.isKeyPressed(LogicalKeyboardKey.backspace)) {
 | 
			
		||||
      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 {
 | 
			
		||||
  @override
 | 
			
		||||
  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 {
 | 
			
		||||
  @override
 | 
			
		||||
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
 | 
			
		||||
@ -494,11 +528,6 @@ class CardExpirationFormatter implements TextInputFormatter {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    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('/', '');
 | 
			
		||||
    StringBuffer buffer = StringBuffer();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user