Repository: kevmoo/qr.dart

Issue:
Feature Request: Support Numeric Mode
Support Mode 1 (Numeric) QR encoding. 

I'm filing this so my following pull request has an issue associated with it.



Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bef20e03..0af9f616 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.1.0
+
+- Added numeric mode support via `addNumeric`
+
 ## 2.0.1-dev
 
 ## 2.0.0
diff --git a/lib/src/byte.dart b/lib/src/byte.dart
index 20575040..96de36a5 100644
--- a/lib/src/byte.dart
+++ b/lib/src/byte.dart
@@ -24,3 +24,50 @@ class QrByte {
     }
   }
 }
+
+/// Encodes numbers (0-9) 10 bits per 3 digits.
+class QrNumeric implements QrByte {
+  factory QrNumeric.fromString(String numberString) {
+    final newList = Uint8List(numberString.length);
+    var count = 0;
+    for (var char in numberString.codeUnits) {
+      if (char < 0x30 || char > 0x39) {
+        throw ArgumentError('string can only contain alpha numeric 0-9');
+      }
+      newList[count++] = char - 0x30;
+    }
+    return QrNumeric._(newList);
+  }
+
+  QrNumeric._(this._data);
+
+  @override
+  final Uint8List _data;
+
+  @override
+  final int mode = qr_mode.modeNumber;
+
+  @override
+  void write(QrBitBuffer buffer) {
+    // Walk through the list of number; attempting to encode up to 3 at a time.
+    // Write (N *3 + 1) bits.
+    final leftOver = _data.length % 3;
+
+    final efficientGrab = _data.length - leftOver;
+    for (var i = 0; i < efficientGrab; i += 3) {
+      final encoded = _data[i] * 100 + _data[i + 1] * 10 + _data[i + 2];
+      buffer.put(encoded, 10);
+    }
+    if (leftOver > 1) {
+      // 2 bytes
+      buffer.put(_data[_data.length - 2] * 10 + _data[_data.length - 1], 7);
+    } else if (leftOver > 0) {
+      // 1 byte
+      buffer.put(_data.last, 4);
+    }
+  }
+
+  // This is still the *number of characters to encode*, not encoded length.
+  @override
+  int get length => _data.length;
+}
diff --git a/lib/src/qr_code.dart b/lib/src/qr_code.dart
index 1a7cf592..9308d5d7 100644
--- a/lib/src/qr_code.dart
+++ b/lib/src/qr_code.dart
@@ -97,6 +97,13 @@ class QrCode {
 
   void addByteData(ByteData data) => _addToList(QrByte.fromByteData(data));
 
+  /// Add QR Numeric Mode data from a string of digits.
+  ///
+  /// It is an error if the [numberString] contains anything other than the
+  /// digits 0 through 9.
+  void addNumeric(String numberString) =>
+      _addToList(QrNumeric.fromString(numberString));
+
   void _addToList(QrByte data) {
     _dataList.add(data);
     _dataCache = null;
diff --git a/pubspec.yaml b/pubspec.yaml
index 61479711..209cf060 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: qr
-version: 2.0.1-dev
+version: 2.1.0
 description: >-
   A QR code generation library for Dart and Flutter.
   Supports QR code version 1 through 40, error correction and redundancy.


Test patch:
diff --git a/test/qr_numeric_test.dart b/test/qr_numeric_test.dart
new file mode 100644
index 00000000..13949d99
--- /dev/null
+++ b/test/qr_numeric_test.dart
@@ -0,0 +1,73 @@
+import 'package:qr/qr.dart';
+import 'package:qr/src/byte.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('all digits 1 through 0', () {
+    final qr = QrNumeric.fromString('1234567890');
+    expect(qr.mode, 1);
+    expect(qr.length, 10);
+    final buffer = QrBitBuffer();
+    qr.write(buffer);
+    expect(buffer.length, 34);
+    expect(
+        buffer.getRange(0, 10).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        123);
+    expect(
+        buffer.getRange(10, 20).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        456);
+    expect(
+        buffer.getRange(20, 30).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        789);
+    expect(
+        buffer.getRange(30, 34).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        0);
+  });
+
+  test('single numeric', () {
+    final qr = QrNumeric.fromString('5');
+    expect(qr.mode, 1);
+    expect(qr.length, 1);
+    final buffer = QrBitBuffer();
+    qr.write(buffer);
+    expect(buffer.length, 4);
+    expect(
+        buffer.getRange(0, 4).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        5);
+  });
+
+  test('double numeric', () {
+    final qr = QrNumeric.fromString('37');
+    expect(qr.mode, 1);
+    expect(qr.length, 2);
+    final buffer = QrBitBuffer();
+    qr.write(buffer);
+    expect(buffer.length, 7, reason: 'n*3+1 = 7');
+    expect(
+        buffer.getRange(0, 7).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        37);
+  });
+
+  test('triple (even) numeric', () {
+    final qr = QrNumeric.fromString('371');
+    expect(qr.mode, 1);
+    expect(qr.length, 3);
+    final buffer = QrBitBuffer();
+    qr.write(buffer);
+    expect(buffer.length, 10, reason: 'n*3+1 = 10');
+    expect(
+        buffer.getRange(0, 10).map<int>((e) => e ? 1 : 0).fold<int>(
+            0, (previousValue, element) => (previousValue << 1) | element),
+        371);
+  });
+
+  test('throws on invalid input', () {
+    expect(() => QrNumeric.fromString('hello'), throwsArgumentError);
+  });
+}