@sincpro/printer-expo 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,10 +8,10 @@ A powerful React Native module for controlling thermal printers in Expo applicat
8
8
 
9
9
  ## ✨ Features
10
10
 
11
- - 🔗 **Bluetooth Connectivity**: Full device discovery and connection management
12
- - 📱 **Permission Management**: Smart handling of Android Bluetooth permissions
13
- - 🖨️ **Advanced Printing**: Receipt printing with flexible line types
14
- - 📋 **QR Code Support**: Print QR codes with customizable sizes
11
+ - 🔗 **Multiple Connectivity**: Bluetooth, WiFi, and USB support
12
+ - 🖨️ **Advanced Printing**: Text, QR codes, barcodes, images, and PDFs
13
+ - 📋 **Structured Receipts**: Header/body/footer with flexible line types
14
+ - ⚙️ **Configurable**: Margins, density, speed, orientation, auto-cutter
15
15
  - 🏗️ **Clean Architecture**: SOLID principles, testable, swappable adapters
16
16
  - 📚 **Official SDK**: Integration with Bixolon SDK (extensible to other brands)
17
17
  - 📝 **TypeScript**: 100% type-safe API with comprehensive definitions
@@ -26,6 +26,12 @@ npm install @sincpro/printer-expo
26
26
  yarn add @sincpro/printer-expo
27
27
  ```
28
28
 
29
+ ### Requirements
30
+
31
+ - **Expo SDK**: `>=52.0.0`
32
+ - **React Native**: Compatible with Expo SDK
33
+ - **Platform**: Android (iOS not currently supported)
34
+
29
35
  ### Post-Installation
30
36
 
31
37
  ```bash
@@ -59,84 +65,79 @@ The module requires Bluetooth permissions. Add to your `app.json`:
59
65
  }
60
66
  ```
61
67
 
62
- **Note**: The module automatically handles runtime permission requests on Android 12+.
63
-
64
- ````
68
+ **Note**: These permissions are required for Bluetooth device discovery and connection on Android 12+.
65
69
 
66
70
  ## 🚀 Quick Start
67
71
 
68
72
  ### Basic Example
69
73
 
70
74
  ```typescript
71
- import Printer, { BluetoothDevice } from '@sincpro/printer-expo';
72
-
73
- // 1. Check Bluetooth status
74
- const isEnabled = await Printer.bluetooth.isEnabled();
75
- const isSupported = await Printer.bluetooth.isSupported();
75
+ import { bluetooth, connection, config, print } from '@sincpro/printer-expo';
76
76
 
77
- // 2. Check permissions
78
- const permStatus = Printer.permission.getStatus();
79
- if (!permStatus.allGranted) {
80
- console.log('Missing permissions:', permStatus.deniedPermissions);
81
- }
82
-
83
- // 3. Get paired devices
84
- const devices = await Printer.bluetooth.getPairedDevices();
77
+ // 1. Get paired devices
78
+ const devices = bluetooth.getPairedDevices();
85
79
  const printers = devices.filter(d => d.isPrinter);
86
80
 
87
- // 4. Connect to printer
88
- await Printer.connection.connect(printers[0].address);
81
+ // 2. Connect to printer via Bluetooth
82
+ await connection.connectBluetooth(printers[0].address, 30000); // 30s timeout
83
+
84
+ // 3. Optional: Configure printer
85
+ await config.set({
86
+ marginLeft: 10,
87
+ marginTop: 5,
88
+ density: 'dark',
89
+ speed: 'medium',
90
+ orientation: 'top_to_bottom',
91
+ autoCutter: { enabled: true, fullCut: true }
92
+ });
89
93
 
90
- // 5. Print receipt
91
- await Printer.print.receipt({
94
+ // 4. Print a simple text
95
+ await print.text('Hello World!', {
96
+ fontSize: 'large',
97
+ alignment: 'center',
98
+ bold: true,
99
+ media: { preset: 'continuous80mm' }
100
+ });
101
+
102
+ // 5. Print a complete receipt
103
+ await print.receipt({
92
104
  header: [
93
105
  { type: 'text', content: 'MY STORE', fontSize: 'large', alignment: 'center', bold: true },
106
+ { type: 'text', content: '123 Main Street', alignment: 'center' },
94
107
  { type: 'separator' },
95
108
  ],
96
- details: [
97
- { type: 'keyValue', key: 'Product', value: '$10.00' },
98
- { type: 'keyValue', key: 'Tax', value: '$1.00' },
109
+ body: [
110
+ { type: 'keyValue', key: 'Product 1', value: '$10.00' },
111
+ { type: 'keyValue', key: 'Product 2', value: '$15.00' },
112
+ { type: 'separator' },
113
+ { type: 'keyValue', key: 'TOTAL', value: '$25.00', bold: true },
99
114
  ],
100
115
  footer: [
101
- { type: 'separator' },
102
- { type: 'keyValue', key: 'TOTAL', value: '$11.00', bold: true },
116
+ { type: 'qr', data: 'https://mystore.com/receipt/123', alignment: 'center' },
117
+ { type: 'text', content: 'Thank you!', alignment: 'center' },
103
118
  { type: 'space', lines: 2 },
104
- ],
105
- });
106
- ````
119
+ ]
120
+ }, { media: { preset: 'continuous80mm' } });
121
+
122
+ // 6. Disconnect
123
+ await connection.disconnect();
124
+ ```
107
125
 
108
126
  ### Complete React Component
109
127
 
110
128
  ```typescript
111
- import React, { useState, useEffect } from 'react';
129
+ import React, { useState } from 'react';
112
130
  import { View, Button, FlatList, Text, Alert } from 'react-native';
113
- import Printer, { BluetoothDevice } from '@sincpro/printer-expo';
131
+ import { bluetooth, connection, print } from '@sincpro/printer-expo';
132
+ import type { BluetoothDevice } from '@sincpro/printer-expo';
114
133
 
115
134
  export default function PrinterScreen() {
116
135
  const [devices, setDevices] = useState<BluetoothDevice[]>([]);
117
136
  const [connected, setConnected] = useState(false);
118
137
 
119
- useEffect(() => {
120
- checkPermissions();
121
- }, []);
122
-
123
- const checkPermissions = () => {
124
- const status = Printer.permission.getStatus();
125
-
126
- if (!status.allGranted) {
127
- Alert.alert('Permissions Required', `Missing: ${status.deniedPermissions.join(', ')}`);
128
- }
129
- };
130
-
131
138
  const scanDevices = async () => {
132
139
  try {
133
- const isEnabled = await Printer.bluetooth.isEnabled();
134
- if (!isEnabled) {
135
- Alert.alert('Error', 'Please enable Bluetooth');
136
- return;
137
- }
138
-
139
- const foundDevices = await Printer.bluetooth.getPairedDevices();
140
+ const foundDevices = bluetooth.getPairedDevices();
140
141
  setDevices(foundDevices.filter((d) => d.isPrinter));
141
142
  } catch (error) {
142
143
  Alert.alert('Error', error.message);
@@ -145,7 +146,7 @@ export default function PrinterScreen() {
145
146
 
146
147
  const connectDevice = async (device: BluetoothDevice) => {
147
148
  try {
148
- await Printer.connection.connect(device.address);
149
+ await connection.connectBluetooth(device.address, 30000);
149
150
  setConnected(true);
150
151
  Alert.alert('Success', `Connected to ${device.name}`);
151
152
  } catch (error) {
@@ -155,12 +156,12 @@ export default function PrinterScreen() {
155
156
 
156
157
  const printTest = async () => {
157
158
  try {
158
- await Printer.print.receipt({
159
+ await print.receipt({
159
160
  header: [
160
161
  { type: 'text', content: 'Test Receipt', fontSize: 'large', alignment: 'center' },
161
162
  { type: 'separator' },
162
163
  ],
163
- details: [{ type: 'text', content: 'This is a test print' }],
164
+ body: [{ type: 'text', content: 'This is a test print' }],
164
165
  footer: [{ type: 'space', lines: 2 }],
165
166
  });
166
167
  Alert.alert('Success', 'Receipt printed');
@@ -186,461 +187,313 @@ export default function PrinterScreen() {
186
187
  }
187
188
  ```
188
189
 
189
- ````
190
-
191
- ## 📱 Detailed Usage
192
-
193
- ### Bluetooth Device Discovery
194
-
195
- ```typescript
196
- // Check Bluetooth permissions
197
- const checkPermissions = async () => {
198
- const permissions = await BixolonPrinter.checkBluetoothPermissions();
199
- console.log('Permissions:', permissions);
200
-
201
- // Check if all required permissions are granted
202
- const allGranted = Object.values(permissions).every((granted) => granted);
203
- if (!allGranted) {
204
- await requestPermissions();
205
- }
206
- };
207
-
208
- // Request permissions if needed
209
- const requestPermissions = async () => {
210
- try {
211
- const granted = await BixolonPrinter.requestBluetoothPermissions();
212
- if (granted) {
213
- console.log('✅ All permissions granted');
214
- } else {
215
- console.log('❌ Some permissions were denied');
216
- }
217
- } catch (error) {
218
- console.error('Permission request failed:', error);
219
- }
220
- };
221
-
222
- // Discover Bluetooth devices
223
- const discoverDevices = async () => {
224
- try {
225
- // Check if Bluetooth is enabled
226
- const isEnabled = await BixolonPrinter.isBluetoothEnabled();
227
- if (!isEnabled) {
228
- Alert.alert('Error', 'Please enable Bluetooth first');
229
- return;
230
- }
190
+ ---
231
191
 
232
- // Start discovery
233
- await BixolonPrinter.startBluetoothDiscovery();
192
+ ## 📚 API Reference
234
193
 
235
- // Get paired devices
236
- const devices = await BixolonPrinter.discoverBluetoothDevices();
237
- console.log('Found devices:', devices);
194
+ ### Connectivity API
238
195
 
239
- // Filter for printer devices
240
- const printers = devices.filter((device) => device.isPrinter);
241
- console.log('Printer devices:', printers);
242
- } catch (error) {
243
- console.error('Device discovery failed:', error);
244
- } finally {
245
- // Stop discovery
246
- await BixolonPrinter.stopBluetoothDiscovery();
247
- }
248
- };
249
- ````
196
+ #### `bluetooth.getPairedDevices(): BluetoothDevice[]`
250
197
 
251
- ### Connecting to a Printer
198
+ Get all paired/bonded Bluetooth devices (synchronous).
252
199
 
253
200
  ```typescript
254
- // Connect to a Bluetooth device
255
- const connectToDevice = async (device: BluetoothDevice) => {
256
- try {
257
- console.log(`Connecting to ${device.name} (${device.address})...`);
258
-
259
- const success = await BixolonPrinter.connectPrinter(
260
- 'BLUETOOTH', // Interface type
261
- device.address, // Device MAC address
262
- 1 // Port (usually 1 for Bluetooth)
263
- );
201
+ import { bluetooth } from '@sincpro/printer-expo';
264
202
 
265
- if (success) {
266
- console.log('✅ Connected to printer successfully');
267
- setIsConnected(true);
268
- } else {
269
- console.log('❌ Connection failed');
270
- }
271
- } catch (error) {
272
- console.error('Connection error:', error);
273
- Alert.alert('Connection Failed', error.message);
274
- }
275
- };
276
-
277
- // Connect via WiFi (if supported)
278
- const connectViaWiFi = async (ipAddress: string, port: number = 9100) => {
279
- try {
280
- const success = await BixolonPrinter.connectPrinter('WIFI', ipAddress, port);
281
-
282
- if (success) {
283
- console.log('✅ Connected via WiFi');
284
- }
285
- } catch (error) {
286
- console.error('WiFi connection failed:', error);
287
- }
288
- };
203
+ const devices = bluetooth.getPairedDevices();
204
+ const printers = devices.filter(d => d.isPrinter);
289
205
  ```
290
206
 
291
- ### Advanced Printing
292
-
293
- ```typescript
294
- // Print formatted text with custom formatting
295
- const printFormattedText = async () => {
296
- try {
297
- const text = `COMPANY NAME
298
- 123 Main Street
299
- City, State 12345
300
-
301
- Invoice #: INV-001
302
- Date: ${new Date().toLocaleDateString()}
303
- Customer: John Doe
207
+ #### `bluetooth.getPairedPrinters(): PairedPrinter[]`
304
208
 
305
- Items:
306
- - Product A $10.00
307
- - Product B $15.00
308
- - Product C $25.00
209
+ Get only paired devices that are printers (synchronous).
309
210
 
310
- Total: $50.00
311
-
312
- Thank you for your business!`;
313
-
314
- const success = await BixolonPrinter.printFormattedText(text, 10);
315
- if (success) {
316
- console.log('✅ Formatted text printed');
317
- }
318
- } catch (error) {
319
- console.error('Print error:', error);
320
- }
321
- };
211
+ ```typescript
212
+ const printers = bluetooth.getPairedPrinters();
213
+ console.log(printers); // [{ name: 'SPP-R200III', address: '00:11:22:AA:BB:CC' }]
214
+ ```
322
215
 
323
- // Print text in pages (for long content)
324
- const printLongText = async () => {
325
- try {
326
- const longText = `This is a very long text that will be split into multiple pages for better printing on thermal printers...`;
216
+ ---
327
217
 
328
- const success = await BixolonPrinter.printTextInPages(longText);
329
- if (success) {
330
- console.log('✅ Long text printed in pages');
331
- }
332
- } catch (error) {
333
- console.error('Page printing error:', error);
334
- }
335
- };
218
+ #### `connection.connectBluetooth(address: string, timeoutMs?: number): Promise<void>`
336
219
 
337
- // Print a complete invoice
338
- const printInvoice = async () => {
339
- try {
340
- const items = [
341
- { description: 'Premium Coffee', quantity: 2, price: 4.5 },
342
- { description: 'Chocolate Croissant', quantity: 1, price: 3.25 },
343
- { description: 'Fresh Orange Juice', quantity: 1, price: 2.75 },
344
- ];
345
-
346
- const success = await BixolonPrinter.printInvoice(
347
- 'Jane Smith', // Customer name
348
- items, // Items array
349
- 15.0 // Total amount
350
- );
351
-
352
- if (success) {
353
- console.log('✅ Invoice printed successfully');
354
- }
355
- } catch (error) {
356
- console.error('Invoice printing error:', error);
357
- }
358
- };
359
- ```
220
+ Connect to a printer via Bluetooth.
360
221
 
361
- ### Disconnecting and Cleanup
222
+ **Parameters:**
223
+ - `address`: MAC address of the printer (e.g., `"00:11:22:AA:BB:CC"`)
224
+ - `timeoutMs`: Connection timeout in milliseconds (default: `10000`)
362
225
 
363
226
  ```typescript
364
- // Disconnect from printer
365
- const disconnect = async () => {
366
- try {
367
- const success = await BixolonPrinter.disconnectPrinter();
368
- if (success) {
369
- console.log('✅ Disconnected from printer');
370
- setIsConnected(false);
371
- }
372
- } catch (error) {
373
- console.error('Disconnection error:', error);
374
- }
375
- };
227
+ import { connection } from '@sincpro/printer-expo';
376
228
 
377
- // Cleanup on component unmount
378
- useEffect(() => {
379
- return () => {
380
- if (isConnected) {
381
- disconnect();
382
- }
383
- };
384
- }, [isConnected]);
229
+ await connection.connectBluetooth('00:11:22:AA:BB:CC', 30000);
385
230
  ```
386
231
 
387
- ## 📋 Local Installation Guide
388
-
389
- ### Creating a Local Package
232
+ #### `connection.connectWifi(ip: string, port?: number, timeoutMs?: number): Promise<void>`
390
233
 
391
- If you want to distribute the module locally without publishing to NPM:
234
+ Connect to a printer via WiFi.
392
235
 
393
- ```bash
394
- # 1. Build the module
395
- cd /path/to/expo-bixolon
396
- npm run build
397
-
398
- # 2. Create a tarball
399
- npm pack
236
+ **Parameters:**
237
+ - `ip`: IP address (e.g., `"192.168.1.100"`)
238
+ - `port`: TCP port (default: `9100`)
239
+ - `timeoutMs`: Connection timeout in milliseconds (default: `10000`)
400
240
 
401
- # This creates: expo-bixolon-0.1.0.tgz
241
+ ```typescript
242
+ await connection.connectWifi('192.168.1.100', 9100, 30000);
402
243
  ```
403
244
 
404
- ### Installing in Another Project
245
+ #### `connection.connectUsb(): Promise<void>`
405
246
 
406
- ```bash
407
- # Method 1: Direct tarball installation
408
- cd /path/to/your-app
409
- npm install /path/to/expo-bixolon-0.1.0.tgz
410
-
411
- # Method 2: Using file path
412
- npm install /absolute/path/to/expo-bixolon
247
+ Connect to a printer via USB.
413
248
 
414
- # Method 3: Using yarn
415
- yarn add /path/to/expo-bixolon-0.1.0.tgz
249
+ ```typescript
250
+ await connection.connectUsb();
416
251
  ```
417
252
 
418
- ### Post-Installation Steps
253
+ #### `connection.disconnect(): Promise<void>`
419
254
 
420
- ```bash
421
- # Install native dependencies
422
- npx expo install
423
-
424
- # Generate native code
425
- npx expo prebuild
255
+ Disconnect from the current printer.
426
256
 
427
- # Run the app
428
- npx expo run:android
429
- # or
430
- npx expo run:ios
257
+ ```typescript
258
+ await connection.disconnect();
431
259
  ```
432
260
 
433
- ## 📚 API Reference
434
-
435
- ### Namespaces
261
+ #### `connection.isConnected(): boolean`
436
262
 
437
- The API is organized into four main namespaces:
263
+ Check if currently connected (synchronous).
438
264
 
439
265
  ```typescript
440
- import Printer from '@sincpro/printer-expo';
441
-
442
- Printer.bluetooth; // Bluetooth operations
443
- Printer.permission; // Permission management
444
- Printer.connection; // Connection control
445
- Printer.print; // Printing operations
266
+ const connected = connection.isConnected();
446
267
  ```
447
268
 
448
- ---
449
-
450
- ### Bluetooth API
269
+ #### `connection.getStatus(): Promise<PrinterStatus>`
451
270
 
452
- #### `bluetooth.isEnabled(): Promise<boolean>`
453
-
454
- Check if Bluetooth is enabled on the device.
271
+ Get current printer status (paper, cover, errors).
455
272
 
456
273
  ```typescript
457
- const enabled = await Printer.bluetooth.isEnabled();
274
+ const status = await connection.getStatus();
275
+ console.log('Connection:', status.connectionState); // 'CONNECTED' | 'CONNECTING' | 'DISCONNECTED' | 'ERROR'
276
+ console.log('Has paper:', status.hasPaper);
277
+ console.log('Cover open:', status.isCoverOpen);
278
+ console.log('Error:', status.errorMessage);
458
279
  ```
459
280
 
460
- #### `bluetooth.isSupported(): Promise<boolean>`
281
+ #### `connection.getInfo(): Promise<PrinterInfo>`
461
282
 
462
- Check if Bluetooth is supported on the device.
283
+ Get printer information (model, firmware, serial).
463
284
 
464
285
  ```typescript
465
- const supported = await Printer.bluetooth.isSupported();
286
+ const info = await connection.getInfo();
287
+ console.log('Model:', info.model);
288
+ console.log('Firmware:', info.firmware);
289
+ console.log('Serial:', info.serial);
290
+ console.log('DPI:', info.dpi);
466
291
  ```
467
292
 
468
- #### `bluetooth.getPairedDevices(): Promise<BluetoothDevice[]>`
293
+ #### `connection.getDpi(): number`
469
294
 
470
- Get list of paired/bonded Bluetooth devices.
295
+ Get printer DPI (synchronous).
471
296
 
472
297
  ```typescript
473
- const devices = await Printer.bluetooth.getPairedDevices();
474
- const printers = devices.filter((d) => d.isPrinter);
298
+ const dpi = connection.getDpi(); // e.g., 203 or 300
475
299
  ```
476
300
 
477
- #### `bluetooth.startDiscovery(): Promise<boolean>`
301
+ ---
302
+
303
+ ### Configuration API
478
304
 
479
- Start Bluetooth discovery to find nearby devices.
305
+ #### `config.set(printerConfig: PrinterConfig): Promise<void>`
306
+
307
+ Set printer configuration (margins, density, speed, orientation, cutter). This sets the default config and applies it immediately if connected.
480
308
 
481
309
  ```typescript
482
- await Printer.bluetooth.startDiscovery();
310
+ import { config } from '@sincpro/printer-expo';
311
+
312
+ await config.set({
313
+ marginLeft: 10, // Left margin in dots
314
+ marginTop: 5, // Top margin in dots
315
+ density: 'dark', // 'light' | 'medium' | 'dark' | 'extra_dark'
316
+ speed: 'medium', // 'slow' | 'medium' | 'fast' | 'extra_fast'
317
+ orientation: 'top_to_bottom', // 'top_to_bottom' | 'bottom_to_top'
318
+ autoCutter: {
319
+ enabled: true,
320
+ fullCut: true // true = full cut, false = partial cut
321
+ }
322
+ });
483
323
  ```
484
324
 
485
- #### `bluetooth.stopDiscovery(): Promise<boolean>`
325
+ #### `config.get(): PrinterConfig`
486
326
 
487
- Stop Bluetooth discovery.
327
+ Get current printer configuration (synchronous).
488
328
 
489
329
  ```typescript
490
- await Printer.bluetooth.stopDiscovery();
330
+ const currentConfig = config.get();
331
+ console.log('Density:', currentConfig.density);
332
+ console.log('Speed:', currentConfig.speed);
491
333
  ```
492
334
 
493
335
  ---
494
336
 
495
- ### Permission API
337
+ ### Print API
338
+
339
+ #### `print.text(text: string, options?: PrintTextOptions): Promise<void>`
496
340
 
497
- #### `permission.hasBluetoothPermissions(): boolean`
341
+ Print a single line of text.
498
342
 
499
- Check if all required Bluetooth permissions are granted (synchronous).
343
+ **Options:**
344
+ - `fontSize`: `'small'` | `'medium'` | `'large'` | `'xlarge'`
345
+ - `alignment`: `'left'` | `'center'` | `'right'`
346
+ - `bold`: `boolean`
347
+ - `media`: `MediaConfig`
500
348
 
501
349
  ```typescript
502
- const hasAll = Printer.permission.hasBluetoothPermissions();
350
+ import { print } from '@sincpro/printer-expo';
351
+
352
+ await print.text('Hello World!', {
353
+ fontSize: 'large',
354
+ alignment: 'center',
355
+ bold: true,
356
+ media: { preset: 'continuous80mm' }
357
+ });
503
358
  ```
504
359
 
505
- #### `permission.getRequiredPermissions(): string[]`
360
+ #### `print.texts(texts: string[], options?: PrintTextsOptions): Promise<void>`
506
361
 
507
- Get list of required permissions for the current Android version.
362
+ Print multiple lines of text.
508
363
 
509
364
  ```typescript
510
- const required = Printer.permission.getRequiredPermissions();
511
- // Returns: ['android.permission.BLUETOOTH_SCAN', 'android.permission.BLUETOOTH_CONNECT', ...]
365
+ await print.texts(
366
+ ['Line 1', 'Line 2', 'Line 3'],
367
+ { fontSize: 'medium', media: { preset: 'continuous80mm' } }
368
+ );
512
369
  ```
513
370
 
514
- #### `permission.getMissingPermissions(): string[]`
371
+ #### `print.qr(data: string, options?: PrintQROptions): Promise<void>`
372
+
373
+ Print a QR code.
515
374
 
516
- Get list of missing/denied permissions.
375
+ **Options:**
376
+ - `size`: QR size 1-10 (default: `5`)
377
+ - `alignment`: `'left'` | `'center'` | `'right'`
378
+ - `media`: `MediaConfig`
517
379
 
518
380
  ```typescript
519
- const missing = Printer.permission.getMissingPermissions();
520
- console.log('Need to request:', missing);
381
+ await print.qr('https://sincpro.com', {
382
+ size: 8,
383
+ alignment: 'center',
384
+ media: { preset: 'continuous80mm' }
385
+ });
521
386
  ```
522
387
 
523
- #### `permission.getStatus(): PermissionStatus`
388
+ #### `print.barcode(data: string, options?: PrintBarcodeOptions): Promise<void>`
524
389
 
525
- Get detailed permission status.
390
+ Print a barcode.
391
+
392
+ **Options:**
393
+ - `type`: `'CODE128'` | `'CODE39'` | `'EAN13'` | `'EAN8'` | `'UPCA'` | `'UPCE'` | `'CODE93'` | `'CODABAR'`
394
+ - `height`: Barcode height in dots
395
+ - `alignment`: `'left'` | `'center'` | `'right'`
396
+ - `media`: `MediaConfig`
526
397
 
527
398
  ```typescript
528
- const status = Printer.permission.getStatus();
529
- console.log('All granted:', status.allGranted);
530
- console.log('Granted:', status.grantedPermissions);
531
- console.log('Denied:', status.deniedPermissions);
532
- console.log('Android version:', status.androidVersion);
399
+ await print.barcode('123456789012', {
400
+ type: 'CODE128',
401
+ height: 80,
402
+ alignment: 'center',
403
+ media: { preset: 'continuous80mm' }
404
+ });
533
405
  ```
534
406
 
535
- ---
407
+ #### `print.imageBase64(base64Data: string, options?: PrintImageOptions): Promise<void>`
536
408
 
537
- ### Connection API
409
+ Print an image from base64 data.
538
410
 
539
- #### `connection.connect(address: string, port?: number): Promise<boolean>`
540
-
541
- Connect to a printer via Bluetooth.
542
-
543
- **Parameters:**
544
-
545
- - `address`: MAC address of the printer (e.g., `"00:11:22:AA:BB:CC"`)
546
- - `port`: TCP port (default: `9100`)
411
+ **Options:**
412
+ - `alignment`: `'left'` | `'center'` | `'right'`
413
+ - `media`: `MediaConfig`
547
414
 
548
415
  ```typescript
549
- await Printer.connection.connect('00:11:22:AA:BB:CC');
416
+ await print.imageBase64(base64ImageData, {
417
+ alignment: 'center',
418
+ media: { preset: 'continuous80mm' }
419
+ });
550
420
  ```
551
421
 
552
- #### `connection.disconnect(): Promise<boolean>`
422
+ #### `print.pdfBase64(base64Data: string, options?: PrintPdfOptions): Promise<void>`
553
423
 
554
- Disconnect from the current printer.
424
+ Print a PDF page from base64 data.
425
+
426
+ **Options:**
427
+ - `page`: Page number to print (default: `0`)
428
+ - `alignment`: `'left'` | `'center'` | `'right'`
429
+ - `media`: `MediaConfig`
555
430
 
556
431
  ```typescript
557
- await Printer.connection.disconnect();
432
+ await print.pdfBase64(base64PdfData, {
433
+ page: 0,
434
+ alignment: 'center',
435
+ media: { preset: 'continuous80mm' }
436
+ });
558
437
  ```
559
438
 
560
- #### `connection.getStatus(): Promise<ConnectionInfo>`
439
+ #### `print.getPdfPageCount(base64Data: string): number`
561
440
 
562
- Get current connection status and details.
441
+ Get page count from a PDF (synchronous).
563
442
 
564
443
  ```typescript
565
- const info = await Printer.connection.getStatus();
566
- console.log('Address:', info.address);
567
- console.log('Type:', info.type); // 'BLUETOOTH' | 'WIFI' | 'USB'
568
- console.log('Status:', info.status); // 'CONNECTED' | 'CONNECTING' | 'DISCONNECTED' | 'ERROR'
444
+ const pageCount = print.getPdfPageCount(base64PdfData);
569
445
  ```
570
446
 
571
- #### `connection.isConnected(): boolean`
447
+ #### `print.keyValue(key: string, value: string, options?: PrintKeyValueOptions): Promise<void>`
572
448
 
573
- Check if currently connected (synchronous).
449
+ Print a key-value pair (two columns).
574
450
 
575
451
  ```typescript
576
- const connected = Printer.connection.isConnected();
452
+ await print.keyValue('Total', '$25.00', {
453
+ fontSize: 'large',
454
+ bold: true,
455
+ media: { preset: 'continuous80mm' }
456
+ });
577
457
  ```
578
458
 
579
- ---
580
-
581
- ### Print API
582
-
583
- #### `print.receipt(receipt: Receipt): Promise<void>`
459
+ #### `print.receipt(receipt: Receipt, options?: PrintReceiptOptions): Promise<void>`
584
460
 
585
- Print a complete receipt with header, details, and footer sections.
461
+ Print a complete structured receipt with header, body, and footer sections.
586
462
 
587
- **Parameters:**
588
-
589
- - `receipt`: Receipt object with sections and configuration
463
+ **Options:**
464
+ - `media`: `MediaConfig`
465
+ - `copies`: Number of copies to print (default: `1`)
590
466
 
591
467
  ```typescript
592
- await Printer.print.receipt({
468
+ await print.receipt({
593
469
  header: [
594
- { type: 'text', content: 'STORE NAME', fontSize: 'large', alignment: 'center', bold: true },
595
- { type: 'text', content: '123 Main St', alignment: 'center' },
470
+ { type: 'text', content: 'MY STORE', fontSize: 'large', alignment: 'center', bold: true },
471
+ { type: 'text', content: '123 Main Street', alignment: 'center' },
596
472
  { type: 'separator' },
597
473
  ],
598
- details: [
599
- { type: 'keyValue', key: 'Product A', value: '$10.00' },
600
- { type: 'keyValue', key: 'Product B', value: '$15.00' },
601
- { type: 'keyValue', key: 'Tax (10%)', value: '$2.50' },
474
+ body: [
475
+ { type: 'keyValue', key: 'Product 1', value: '$10.00' },
476
+ { type: 'keyValue', key: 'Product 2', value: '$15.00' },
477
+ { type: 'separator' },
478
+ { type: 'keyValue', key: 'Tax', value: '$2.50' },
602
479
  ],
603
480
  footer: [
604
481
  { type: 'separator' },
605
482
  { type: 'keyValue', key: 'TOTAL', value: '$27.50', bold: true, fontSize: 'large' },
606
- { type: 'qrCode', data: 'https://mystore.com/receipt/12345', size: 5, alignment: 'center' },
483
+ { type: 'qr', data: 'https://mystore.com/receipt/123', alignment: 'center', size: 6 },
484
+ { type: 'text', content: 'Thank you for your purchase!', alignment: 'center' },
607
485
  { type: 'space', lines: 2 },
608
- ],
609
- copies: 1,
610
- });
611
- ```
612
-
613
- #### `print.lines(lines: ReceiptLine[]): Promise<void>`
614
-
615
- Print a list of receipt lines without sections.
616
-
617
- ```typescript
618
- await Printer.print.lines([
619
- { type: 'text', content: 'Quick Receipt', alignment: 'center' },
620
- { type: 'separator' },
621
- { type: 'keyValue', key: 'Item', value: '$5.00' },
622
- { type: 'space', lines: 1 },
623
- ]);
624
- ```
625
-
626
- #### `print.qrCode(data: string, size?: number): Promise<void>`
627
-
628
- Print a standalone QR code.
629
-
630
- **Parameters:**
631
-
632
- - `data`: QR code content (URL, text, etc.)
633
- - `size`: QR code size 1-10 (default: `5`)
634
-
635
- ```typescript
636
- await Printer.print.qrCode('https://example.com', 7);
486
+ ]
487
+ }, { media: { preset: 'continuous80mm' }, copies: 1 });
637
488
  ```
638
489
 
639
490
  ---
640
491
 
641
- ## 🎨 Receipt Line Types
492
+ ### Receipt Line Types
642
493
 
643
- ### TextLine
494
+ Receipt lines are the building blocks of structured receipts. Each line type has specific properties.
495
+
496
+ #### `TextLine`
644
497
 
645
498
  Print formatted text with customizable style.
646
499
 
@@ -655,87 +508,203 @@ Print formatted text with customizable style.
655
508
  ```
656
509
 
657
510
  **Example:**
658
-
659
511
  ```typescript
660
512
  { type: 'text', content: 'INVOICE', fontSize: 'xlarge', alignment: 'center', bold: true }
661
513
  ```
662
514
 
663
- ### KeyValueLine
515
+ #### `KeyValueLine`
664
516
 
665
- Print key-value pairs (common in receipts).
517
+ Print key-value pairs in two columns (common in receipts).
666
518
 
667
519
  ```typescript
668
520
  {
669
521
  type: 'keyValue',
670
522
  key: string,
671
523
  value: string,
672
- fontSize?: FontSize,
524
+ fontSize?: 'small' | 'medium' | 'large' | 'xlarge',
673
525
  bold?: boolean
674
526
  }
675
527
  ```
676
528
 
677
529
  **Example:**
678
-
679
530
  ```typescript
680
531
  { type: 'keyValue', key: 'Subtotal', value: '$25.00' }
681
- { type: 'keyValue', key: 'TOTAL', value: '$27.50', bold: true }
532
+ { type: 'keyValue', key: 'TOTAL', value: '$27.50', bold: true, fontSize: 'large' }
682
533
  ```
683
534
 
684
- ### QRCodeLine
535
+ #### `QRLine`
685
536
 
686
537
  Embed QR codes in receipts.
687
538
 
688
539
  ```typescript
689
540
  {
690
- type: 'qrCode',
541
+ type: 'qr',
542
+ data: string,
543
+ size?: number, // 1-10, default: 5
544
+ alignment?: 'left' | 'center' | 'right'
545
+ }
546
+ ```
547
+
548
+ **Example:**
549
+ ```typescript
550
+ { type: 'qr', data: 'https://store.com/receipt/12345', size: 6, alignment: 'center' }
551
+ ```
552
+
553
+ #### `BarcodeLine`
554
+
555
+ Embed barcodes in receipts.
556
+
557
+ ```typescript
558
+ {
559
+ type: 'barcode',
691
560
  data: string,
692
- size?: number, // 1-10, default: 5
693
- alignment?: Alignment
561
+ barcodeType?: 'CODE128' | 'CODE39' | 'EAN13' | 'EAN8' | 'UPCA' | 'UPCE' | 'CODE93' | 'CODABAR',
562
+ height?: number,
563
+ alignment?: 'left' | 'center' | 'right'
694
564
  }
695
565
  ```
696
566
 
697
567
  **Example:**
568
+ ```typescript
569
+ { type: 'barcode', data: '123456789012', barcodeType: 'EAN13', height: 80, alignment: 'center' }
570
+ ```
571
+
572
+ #### `ImageLine`
698
573
 
574
+ Embed images (base64) in receipts.
575
+
576
+ ```typescript
577
+ {
578
+ type: 'image',
579
+ base64: string,
580
+ alignment?: 'left' | 'center' | 'right'
581
+ }
582
+ ```
583
+
584
+ **Example:**
699
585
  ```typescript
700
- { type: 'qrCode', data: 'https://store.com/receipt/12345', size: 6, alignment: 'center' }
586
+ { type: 'image', base64: 'iVBORw0KGgoAAAANS...', alignment: 'center' }
701
587
  ```
702
588
 
703
- ### SeparatorLine
589
+ #### `SeparatorLine`
704
590
 
705
591
  Print horizontal separator lines.
706
592
 
707
593
  ```typescript
708
594
  {
709
595
  type: 'separator',
710
- char?: string, // Character to repeat, default: '-'
711
- length?: number // Line length, default: 48
596
+ char?: string, // Character to repeat, default: '-'
597
+ length?: number // Line length in characters, default: 48
712
598
  }
713
599
  ```
714
600
 
715
- **Example:**
716
-
601
+ **Examples:**
717
602
  ```typescript
718
603
  { type: 'separator' }
719
604
  { type: 'separator', char: '=', length: 32 }
720
605
  ```
721
606
 
722
- ### SpaceLine
607
+ #### `SpaceLine`
723
608
 
724
609
  Add blank lines for spacing.
725
610
 
726
611
  ```typescript
727
612
  {
728
613
  type: 'space',
729
- lines?: number // Number of blank lines, default: 1
614
+ lines?: number // Number of blank lines, default: 1
730
615
  }
731
616
  ```
732
617
 
733
618
  **Example:**
734
-
735
619
  ```typescript
736
620
  { type: 'space', lines: 2 }
737
621
  ```
738
622
 
623
+ #### `ColumnsLine`
624
+
625
+ Print multiple columns in one row.
626
+
627
+ ```typescript
628
+ {
629
+ type: 'columns',
630
+ columns: Array<{
631
+ text: string,
632
+ widthRatio?: number,
633
+ alignment?: 'left' | 'center' | 'right'
634
+ }>,
635
+ fontSize?: 'small' | 'medium' | 'large' | 'xlarge',
636
+ bold?: boolean
637
+ }
638
+ ```
639
+
640
+ **Example:**
641
+ ```typescript
642
+ {
643
+ type: 'columns',
644
+ columns: [
645
+ { text: 'Item', widthRatio: 2, alignment: 'left' },
646
+ { text: 'Qty', widthRatio: 1, alignment: 'center' },
647
+ { text: 'Price', widthRatio: 1, alignment: 'right' }
648
+ ],
649
+ bold: true
650
+ }
651
+ ```
652
+
653
+ ---
654
+
655
+ ### MediaConfig - Paper Configuration
656
+
657
+ Configure paper/label dimensions for printing. You can use presets or custom configurations.
658
+
659
+ #### Using Presets
660
+
661
+ ```typescript
662
+ // Continuous paper presets
663
+ { preset: 'continuous58mm' } // 58mm continuous paper
664
+ { preset: 'continuous72mm' } // 72mm continuous paper
665
+ { preset: 'continuous80mm' } // 80mm continuous paper (most common)
666
+ ```
667
+
668
+ #### Custom Configuration (Millimeters)
669
+
670
+ ```typescript
671
+ // Custom continuous paper
672
+ {
673
+ widthMm: 72,
674
+ type: 'continuous'
675
+ }
676
+
677
+ // Labels with gap
678
+ {
679
+ widthMm: 50,
680
+ heightMm: 30,
681
+ gapMm: 3,
682
+ type: 'gap'
683
+ }
684
+
685
+ // Labels with black mark
686
+ {
687
+ widthMm: 60,
688
+ heightMm: 40,
689
+ type: 'black_mark'
690
+ }
691
+ ```
692
+
693
+ #### Custom Configuration (Dots)
694
+
695
+ If you need precise control, you can specify dimensions in dots (based on printer DPI).
696
+
697
+ ```typescript
698
+ {
699
+ widthDots: 576, // 72mm at 203 DPI
700
+ heightDots: 240, // 30mm at 203 DPI
701
+ gapDots: 24, // 3mm at 203 DPI
702
+ type: 'gap'
703
+ }
704
+ ```
705
+
706
+ **Note:** The module automatically converts millimeters to dots based on the printer's DPI. Using millimeters is recommended for easier configuration.
707
+
739
708
  ---
740
709
 
741
710
  ## 📦 TypeScript Types
@@ -744,44 +713,58 @@ Add blank lines for spacing.
744
713
 
745
714
  ```typescript
746
715
  interface BluetoothDevice {
747
- name: string;
748
- address: string;
749
- type: 'CLASSIC' | 'LE' | 'DUAL' | 'UNKNOWN';
750
- isPrinter: boolean;
716
+ name: string; // Device name
717
+ address: string; // MAC address (e.g., "00:11:22:AA:BB:CC")
718
+ isPrinter: boolean; // True if device is identified as a printer
751
719
  }
752
720
  ```
753
721
 
754
- ### PermissionStatus
722
+ ### PairedPrinter
755
723
 
756
724
  ```typescript
757
- interface PermissionStatus {
758
- allGranted: boolean;
759
- grantedPermissions: string[];
760
- deniedPermissions: string[];
761
- androidVersion: number;
725
+ interface PairedPrinter {
726
+ name: string; // Printer name
727
+ address: string; // MAC address
762
728
  }
763
729
  ```
764
730
 
765
- ### ConnectionInfo
731
+ ### PrinterStatus
766
732
 
767
733
  ```typescript
768
- interface ConnectionInfo {
769
- address: string;
770
- port: number;
771
- type: 'BLUETOOTH' | 'WIFI' | 'USB' | 'UNKNOWN';
772
- status: 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'ERROR';
734
+ interface PrinterStatus {
735
+ connectionState: 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'ERROR';
736
+ hasPaper: boolean; // True if paper is available
737
+ isCoverOpen: boolean; // True if printer cover is open
738
+ isOverheated: boolean; // True if printer is overheated
739
+ hasError: boolean; // True if printer has an error
740
+ errorMessage: string | null; // Error message if hasError is true
773
741
  }
774
742
  ```
775
743
 
776
- ### Receipt
744
+ ### PrinterInfo
777
745
 
778
746
  ```typescript
779
- interface Receipt {
780
- header?: ReceiptLine[];
781
- details?: ReceiptLine[];
782
- footer?: ReceiptLine[];
783
- mediaConfig?: MediaConfig;
784
- copies?: number;
747
+ interface PrinterInfo {
748
+ model: string; // Printer model (e.g., "SPP-R200III")
749
+ firmware: string; // Firmware version
750
+ serial: string; // Serial number
751
+ dpi: number; // Printer DPI (e.g., 203 or 300)
752
+ }
753
+ ```
754
+
755
+ ### PrinterConfig
756
+
757
+ ```typescript
758
+ interface PrinterConfig {
759
+ marginLeft?: number; // Left margin in dots
760
+ marginTop?: number; // Top margin in dots
761
+ density?: 'light' | 'medium' | 'dark' | 'extra_dark';
762
+ speed?: 'slow' | 'medium' | 'fast' | 'extra_fast';
763
+ orientation?: 'top_to_bottom' | 'bottom_to_top';
764
+ autoCutter?: {
765
+ enabled: boolean; // Enable auto cutter
766
+ fullCut?: boolean; // Full cut (true) or partial cut (false)
767
+ };
785
768
  }
786
769
  ```
787
770
 
@@ -789,128 +772,283 @@ interface Receipt {
789
772
 
790
773
  ```typescript
791
774
  interface MediaConfig {
792
- type: 'continuous' | 'labelGap' | 'labelBlackMark';
793
- width: number;
794
- height: number;
795
- offset?: number;
796
- gap?: number;
775
+ // Use preset for common paper sizes
776
+ preset?: 'continuous58mm' | 'continuous72mm' | 'continuous80mm';
777
+
778
+ // Or custom configuration in millimeters
779
+ widthMm?: number; // Paper width in mm
780
+ heightMm?: number; // Label height in mm (for labels)
781
+ gapMm?: number; // Gap size in mm (for labels)
782
+
783
+ // Or custom configuration in dots
784
+ widthDots?: number; // Paper width in dots
785
+ heightDots?: number; // Label height in dots
786
+ gapDots?: number; // Gap size in dots
787
+
788
+ // Media type
789
+ type?: 'continuous' | 'gap' | 'label' | 'black_mark';
790
+ }
791
+ ```
792
+
793
+ ### Receipt
794
+
795
+ ```typescript
796
+ interface Receipt {
797
+ header?: ReceiptLine[]; // Header section (logo, store info)
798
+ body?: ReceiptLine[]; // Body section (items, details)
799
+ footer?: ReceiptLine[]; // Footer section (totals, QR, thanks)
797
800
  }
798
801
  ```
799
802
 
803
+ ### ReceiptLine Types
804
+
805
+ ```typescript
806
+ type ReceiptLine =
807
+ | TextLine
808
+ | KeyValueLine
809
+ | QRLine
810
+ | BarcodeLine
811
+ | ImageLine
812
+ | SeparatorLine
813
+ | SpaceLine
814
+ | ColumnsLine;
815
+ ```
816
+
800
817
  ---
801
818
 
802
- ## 🎯 Advanced Examples
819
+ ## 🎯 Usage Examples
803
820
 
804
- ### Complete Receipt with All Line Types
821
+ ### Example 1: Basic Connection and Text Printing
805
822
 
806
823
  ```typescript
807
- const fullReceipt: Receipt = {
808
- header: [
809
- {
810
- type: 'text',
811
- content: '🏪 MY RETAIL STORE',
812
- fontSize: 'xlarge',
824
+ import { bluetooth, connection, print } from '@sincpro/printer-expo';
825
+
826
+ async function basicPrint() {
827
+ try {
828
+ // 1. Get paired printers
829
+ const printers = bluetooth.getPairedPrinters();
830
+ if (printers.length === 0) {
831
+ throw new Error('No paired printers found');
832
+ }
833
+
834
+ // 2. Connect to first printer
835
+ await connection.connectBluetooth(printers[0].address, 30000);
836
+ console.log('Connected to', printers[0].name);
837
+
838
+ // 3. Print simple text
839
+ await print.text('Hello from Expo!', {
840
+ fontSize: 'large',
813
841
  alignment: 'center',
814
842
  bold: true,
815
- },
816
- { type: 'text', content: '123 Commerce Street', alignment: 'center' },
817
- { type: 'text', content: 'Phone: (555) 123-4567', alignment: 'center' },
818
- { type: 'separator', char: '=' },
819
- { type: 'text', content: 'SALES RECEIPT', fontSize: 'large', alignment: 'center' },
820
- { type: 'separator', char: '=' },
821
- { type: 'space' },
822
- ],
823
- details: [
824
- { type: 'keyValue', key: 'Date', value: new Date().toLocaleDateString() },
825
- { type: 'keyValue', key: 'Receipt #', value: '00123' },
826
- { type: 'keyValue', key: 'Cashier', value: 'John Doe' },
827
- { type: 'space' },
828
- { type: 'separator' },
829
- { type: 'text', content: 'ITEMS', bold: true },
830
- { type: 'separator' },
831
- { type: 'keyValue', key: 'Coffee (x2)', value: '$9.00' },
832
- { type: 'keyValue', key: 'Croissant (x1)', value: '$3.50' },
833
- { type: 'keyValue', key: 'Orange Juice (x1)', value: '$4.00' },
834
- { type: 'space' },
835
- { type: 'separator' },
836
- { type: 'keyValue', key: 'Subtotal', value: '$16.50' },
837
- { type: 'keyValue', key: 'Tax (8%)', value: '$1.32' },
838
- ],
839
- footer: [
840
- { type: 'separator', char: '=' },
841
- { type: 'keyValue', key: 'TOTAL', value: '$17.82', fontSize: 'large', bold: true },
842
- { type: 'separator', char: '=' },
843
- { type: 'space', lines: 2 },
844
- { type: 'text', content: 'Thank you for your purchase!', alignment: 'center' },
845
- { type: 'text', content: 'Visit us at www.mystore.com', alignment: 'center' },
846
- { type: 'space' },
847
- { type: 'qrCode', data: 'https://mystore.com/receipt/00123', size: 6, alignment: 'center' },
848
- { type: 'space', lines: 3 },
849
- ],
850
- copies: 1,
851
- };
843
+ media: { preset: 'continuous80mm' }
844
+ });
845
+
846
+ // 4. Disconnect
847
+ await connection.disconnect();
848
+ console.log('Print complete!');
849
+ } catch (error) {
850
+ console.error('Print failed:', error);
851
+ }
852
+ }
853
+ ```
854
+
855
+ ### Example 2: Sales Receipt
856
+
857
+ ```typescript
858
+ import { connection, print } from '@sincpro/printer-expo';
859
+
860
+ async function printSalesReceipt(items: Array<{ name: string; qty: number; price: number }>) {
861
+ const subtotal = items.reduce((sum, item) => sum + (item.qty * item.price), 0);
862
+ const tax = subtotal * 0.08; // 8% tax
863
+ const total = subtotal + tax;
864
+
865
+ await print.receipt({
866
+ header: [
867
+ { type: 'text', content: '🏪 MY RETAIL STORE', fontSize: 'xlarge', alignment: 'center', bold: true },
868
+ { type: 'text', content: '123 Commerce Street', alignment: 'center' },
869
+ { type: 'text', content: 'Phone: (555) 123-4567', alignment: 'center' },
870
+ { type: 'separator', char: '=' },
871
+ { type: 'text', content: 'SALES RECEIPT', fontSize: 'large', alignment: 'center' },
872
+ { type: 'separator', char: '=' },
873
+ { type: 'space' },
874
+ { type: 'keyValue', key: 'Date', value: new Date().toLocaleDateString() },
875
+ { type: 'keyValue', key: 'Receipt #', value: 'RCP-' + Date.now() },
876
+ { type: 'space' },
877
+ { type: 'separator' },
878
+ ],
879
+ body: [
880
+ { type: 'text', content: 'ITEMS', bold: true },
881
+ { type: 'separator' },
882
+ ...items.map(item => ({
883
+ type: 'keyValue' as const,
884
+ key: `${item.name} (x${item.qty})`,
885
+ value: `$${(item.qty * item.price).toFixed(2)}`
886
+ })),
887
+ { type: 'space' },
888
+ { type: 'separator' },
889
+ { type: 'keyValue', key: 'Subtotal', value: `$${subtotal.toFixed(2)}` },
890
+ { type: 'keyValue', key: 'Tax (8%)', value: `$${tax.toFixed(2)}` },
891
+ ],
892
+ footer: [
893
+ { type: 'separator', char: '=' },
894
+ { type: 'keyValue', key: 'TOTAL', value: `$${total.toFixed(2)}`, fontSize: 'large', bold: true },
895
+ { type: 'separator', char: '=' },
896
+ { type: 'space', lines: 2 },
897
+ { type: 'text', content: 'Thank you for your purchase!', alignment: 'center' },
898
+ { type: 'text', content: 'Visit us at www.mystore.com', alignment: 'center' },
899
+ { type: 'space' },
900
+ { type: 'qr', data: `https://mystore.com/receipt/${Date.now()}`, size: 6, alignment: 'center' },
901
+ { type: 'space', lines: 3 },
902
+ ]
903
+ }, { media: { preset: 'continuous80mm' } });
904
+ }
905
+
906
+ // Usage
907
+ printSalesReceipt([
908
+ { name: 'Coffee', qty: 2, price: 4.50 },
909
+ { name: 'Croissant', qty: 1, price: 3.25 },
910
+ { name: 'Orange Juice', qty: 1, price: 2.75 }
911
+ ]);
912
+ ```
913
+
914
+ ### Example 3: Product Label Printing
852
915
 
853
- await Printer.print.receipt(fullReceipt);
916
+ ```typescript
917
+ import { config, print } from '@sincpro/printer-expo';
918
+
919
+ async function printProductLabel(product: { name: string; sku: string; price: number }) {
920
+ // Configure for label printing
921
+ await config.set({
922
+ marginLeft: 5,
923
+ marginTop: 5,
924
+ density: 'dark',
925
+ speed: 'medium'
926
+ });
927
+
928
+ await print.receipt({
929
+ header: [
930
+ { type: 'text', content: product.name, fontSize: 'large', alignment: 'center', bold: true },
931
+ { type: 'space' },
932
+ ],
933
+ body: [
934
+ { type: 'barcode', data: product.sku, barcodeType: 'CODE128', height: 60, alignment: 'center' },
935
+ { type: 'space' },
936
+ { type: 'text', content: `SKU: ${product.sku}`, alignment: 'center' },
937
+ ],
938
+ footer: [
939
+ { type: 'space' },
940
+ { type: 'text', content: `$${product.price.toFixed(2)}`, fontSize: 'xlarge', alignment: 'center', bold: true },
941
+ ]
942
+ }, {
943
+ media: {
944
+ widthMm: 50,
945
+ heightMm: 30,
946
+ gapMm: 3,
947
+ type: 'gap'
948
+ }
949
+ });
950
+ }
951
+
952
+ // Usage
953
+ printProductLabel({
954
+ name: 'Premium Coffee Beans',
955
+ sku: '1234567890',
956
+ price: 12.99
957
+ });
854
958
  ```
855
959
 
856
- ### Error Handling Pattern
960
+ ### Example 4: Error Handling Pattern
857
961
 
858
962
  ```typescript
859
- const safePrint = async (receipt: Receipt) => {
963
+ import { connection, print } from '@sincpro/printer-expo';
964
+ import type { Receipt } from '@sincpro/printer-expo';
965
+
966
+ async function safePrint(receipt: Receipt) {
860
967
  try {
861
968
  // Check connection
862
- if (!Printer.connection.isConnected()) {
969
+ if (!connection.isConnected()) {
863
970
  throw new Error('Printer not connected');
864
971
  }
865
972
 
866
- // Check status
867
- const status = await Printer.connection.getStatus();
868
- if (status.status !== 'CONNECTED') {
869
- throw new Error(`Connection ${status.status.toLowerCase()}`);
973
+ // Check printer status
974
+ const status = await connection.getStatus();
975
+
976
+ if (status.connectionState !== 'CONNECTED') {
977
+ throw new Error(`Printer is ${status.connectionState.toLowerCase()}`);
978
+ }
979
+
980
+ if (!status.hasPaper) {
981
+ throw new Error('Printer is out of paper');
982
+ }
983
+
984
+ if (status.isCoverOpen) {
985
+ throw new Error('Printer cover is open');
986
+ }
987
+
988
+ if (status.isOverheated) {
989
+ throw new Error('Printer is overheated');
990
+ }
991
+
992
+ if (status.hasError) {
993
+ throw new Error(`Printer error: ${status.errorMessage}`);
870
994
  }
871
995
 
872
- // Print
873
- await Printer.print.receipt(receipt);
996
+ // Print receipt
997
+ await print.receipt(receipt, { media: { preset: 'continuous80mm' } });
998
+
874
999
  console.log('✅ Print successful');
1000
+ return { success: true };
875
1001
  } catch (error) {
876
- console.error('❌ Print failed:', error.message);
877
-
878
- // Show user-friendly message
879
- Alert.alert('Print Error', error.message || 'Could not print receipt', [{ text: 'OK' }]);
1002
+ console.error('❌ Print failed:', error);
1003
+
1004
+ // Return user-friendly error
1005
+ return {
1006
+ success: false,
1007
+ error: error instanceof Error ? error.message : 'Unknown error'
1008
+ };
880
1009
  }
881
- };
1010
+ }
1011
+
1012
+ // Usage in React component
1013
+ function PrintButton() {
1014
+ const handlePrint = async () => {
1015
+ const result = await safePrint(myReceipt);
1016
+
1017
+ if (result.success) {
1018
+ Alert.alert('Success', 'Receipt printed successfully');
1019
+ } else {
1020
+ Alert.alert('Print Error', result.error);
1021
+ }
1022
+ };
1023
+
1024
+ return <Button title="Print" onPress={handlePrint} />;
1025
+ }
882
1026
  ```
883
1027
 
884
- ### Custom Hook for Printer Management
1028
+ ### Example 5: Custom Hook for Printer Management
885
1029
 
886
1030
  ```typescript
887
1031
  import { useState, useEffect } from 'react';
888
- import Printer, { BluetoothDevice, ConnectionInfo } from '@sincpro/printer-expo';
1032
+ import { bluetooth, connection, print } from '@sincpro/printer-expo';
1033
+ import type { BluetoothDevice, PrinterStatus, Receipt } from '@sincpro/printer-expo';
889
1034
 
890
1035
  export function usePrinter() {
891
1036
  const [devices, setDevices] = useState<BluetoothDevice[]>([]);
892
1037
  const [connected, setConnected] = useState(false);
893
- const [connectionInfo, setConnectionInfo] = useState<ConnectionInfo | null>(null);
1038
+ const [status, setStatus] = useState<PrinterStatus | null>(null);
894
1039
 
895
1040
  useEffect(() => {
896
1041
  checkConnection();
897
1042
  }, []);
898
1043
 
899
1044
  const checkConnection = () => {
900
- const isConnected = Printer.connection.isConnected();
901
- setConnected(isConnected);
1045
+ setConnected(connection.isConnected());
902
1046
  };
903
1047
 
904
- const scanDevices = async () => {
1048
+ const scanDevices = () => {
905
1049
  try {
906
- const isEnabled = await Printer.bluetooth.isEnabled();
907
- if (!isEnabled) {
908
- throw new Error('Bluetooth is disabled');
909
- }
910
-
911
- const foundDevices = await Printer.bluetooth.getPairedDevices();
912
- setDevices(foundDevices.filter((d) => d.isPrinter));
913
-
1050
+ const foundDevices = bluetooth.getPairedDevices();
1051
+ setDevices(foundDevices.filter(d => d.isPrinter));
914
1052
  return foundDevices;
915
1053
  } catch (error) {
916
1054
  console.error('Scan failed:', error);
@@ -918,14 +1056,14 @@ export function usePrinter() {
918
1056
  }
919
1057
  };
920
1058
 
921
- const connect = async (address: string) => {
1059
+ const connect = async (address: string, timeoutMs = 30000) => {
922
1060
  try {
923
- await Printer.connection.connect(address);
1061
+ await connection.connectBluetooth(address, timeoutMs);
924
1062
  setConnected(true);
925
-
926
- const info = await Printer.connection.getStatus();
927
- setConnectionInfo(info);
928
-
1063
+
1064
+ const printerStatus = await connection.getStatus();
1065
+ setStatus(printerStatus);
1066
+
929
1067
  return true;
930
1068
  } catch (error) {
931
1069
  console.error('Connection failed:', error);
@@ -935,9 +1073,9 @@ export function usePrinter() {
935
1073
 
936
1074
  const disconnect = async () => {
937
1075
  try {
938
- await Printer.connection.disconnect();
1076
+ await connection.disconnect();
939
1077
  setConnected(false);
940
- setConnectionInfo(null);
1078
+ setStatus(null);
941
1079
  return true;
942
1080
  } catch (error) {
943
1081
  console.error('Disconnection failed:', error);
@@ -950,32 +1088,45 @@ export function usePrinter() {
950
1088
  throw new Error('Not connected to printer');
951
1089
  }
952
1090
 
953
- await Printer.print.receipt(receipt);
1091
+ await print.receipt(receipt, { media: { preset: 'continuous80mm' } });
1092
+ };
1093
+
1094
+ const refreshStatus = async () => {
1095
+ if (connected) {
1096
+ const printerStatus = await connection.getStatus();
1097
+ setStatus(printerStatus);
1098
+ return printerStatus;
1099
+ }
1100
+ return null;
954
1101
  };
955
1102
 
956
1103
  return {
957
1104
  devices,
958
1105
  connected,
959
- connectionInfo,
1106
+ status,
960
1107
  scanDevices,
961
1108
  connect,
962
1109
  disconnect,
963
1110
  printReceipt,
1111
+ refreshStatus,
964
1112
  };
965
1113
  }
966
1114
 
967
- // Usage
968
- function MyComponent() {
1115
+ // Usage in component
1116
+ function MyPrinterComponent() {
969
1117
  const printer = usePrinter();
970
1118
 
971
1119
  return (
972
1120
  <View>
973
- <Button title="Scan" onPress={printer.scanDevices} />
974
- <Button
975
- title="Print"
1121
+ <Button title="Scan Printers" onPress={printer.scanDevices} />
1122
+ <Button
1123
+ title="Print Receipt"
976
1124
  onPress={() => printer.printReceipt(myReceipt)}
977
1125
  disabled={!printer.connected}
978
1126
  />
1127
+ {printer.status && (
1128
+ <Text>Status: {printer.status.connectionState}</Text>
1129
+ )}
979
1130
  </View>
980
1131
  );
981
1132
  }
@@ -985,106 +1136,276 @@ function MyComponent() {
985
1136
 
986
1137
  ## 🏗️ Architecture
987
1138
 
988
- This module follows **Clean Architecture** with **Hexagonal Architecture** (Ports & Adapters) principles:
1139
+ This module follows **Clean Architecture** with **Hexagonal Architecture** (Ports & Adapters) principles for maximum maintainability and extensibility.
1140
+
1141
+ ### Architecture Layers
989
1142
 
990
1143
  ```
1144
+ ┌─────────────────────────────────────────────────────────┐
1145
+ │ Dependencies ALWAYS point inward (toward Domain) │
1146
+ └─────────────────────────────────────────────────────────┘
1147
+
991
1148
  TypeScript (React Native)
992
1149
 
993
- ENTRYPOINT - Expo Modules API bridge
994
-
995
- SERVICE - Use cases & orchestration
996
-
997
- DOMAIN - Business entities & rules
1150
+ ENTRYPOINT Expo Modules API bridge
998
1151
 
999
- ADAPTER - Printer implementations (Bixolon, Zebra, etc.)
1152
+ SERVICE Use cases & orchestration
1000
1153
 
1001
- INFRASTRUCTURE - Android APIs & SDKs
1154
+ DOMAIN Business entities & rules (interfaces)
1155
+
1156
+ ┌─────┴─────┐
1157
+ ADAPTER INFRASTRUCTURE
1158
+ (Vendors) (Platform APIs)
1002
1159
  ```
1003
1160
 
1004
- **Benefits:**
1161
+ ### Layer Responsibilities
1005
1162
 
1006
- - **Testable**: Mock adapters and services
1163
+ | Layer | Purpose | Examples |
1164
+ |-------|---------|----------|
1165
+ | **TypeScript** | React Native API | `bluetooth`, `connection`, `config`, `print` |
1166
+ | **Entrypoint** | Expo ↔ Kotlin bridge | `PrinterModule.kt` |
1167
+ | **Service** | Business logic | `ConnectivityService`, `PrintService` |
1168
+ | **Domain** | Contracts & entities | `IPrinter`, `Receipt`, `MediaConfig` |
1169
+ | **Adapter** | Vendor SDKs | `BixolonPrinterAdapter` |
1170
+ | **Infrastructure** | Platform utilities | `AndroidBluetoothProvider`, `BinaryConverter` |
1171
+
1172
+ ### Benefits
1173
+
1174
+ - ✅ **Testable**: Mock adapters and services independently
1007
1175
  - ✅ **Maintainable**: Clear separation of concerns
1008
- - ✅ **Extensible**: Easy to add new printer brands
1176
+ - ✅ **Extensible**: Easy to add new printer brands (Zebra, Epson, Star, etc.)
1009
1177
  - ✅ **Swappable**: Change implementations without affecting business logic
1178
+ - ✅ **Framework-independent**: Domain layer has no Android/iOS dependencies
1179
+
1180
+ ### Adding New Printer Brands
1010
1181
 
1011
- See [ARCHITECTURE.md](ARCHITECTURE.md) for details.
1182
+ The architecture makes it easy to support additional printer brands:
1183
+
1184
+ 1. **Create Adapter**: Implement `IPrinter` interface for new vendor SDK
1185
+ 2. **Register in SDK**: Add to `SincproPrinterSdk` entry point
1186
+ 3. **No changes needed**: Business logic and API remain unchanged
1187
+
1188
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed architecture documentation.
1012
1189
 
1013
1190
  ---
1014
1191
 
1015
- ## 🛠️ Troubleshooting
1192
+ ## 🖨️ Supported Printers
1016
1193
 
1017
- ### Bluetooth Permissions
1194
+ ### Bixolon Printers
1018
1195
 
1019
- **Problem**: "Permission denied" errors
1196
+ This module currently supports **Bixolon thermal printers** via the official Bixolon SDK.
1020
1197
 
1021
- **Solution**: Check permission status and request if needed
1198
+ #### Tested Models
1022
1199
 
1023
- ```typescript
1024
- const status = Printer.permission.getStatus();
1025
- if (!status.allGranted) {
1026
- console.log('Missing:', status.deniedPermissions);
1027
- // Guide user to app settings to grant permissions
1028
- }
1029
- ```
1200
+ - ✅ **SPP-R200III** - 2" mobile printer (58mm)
1201
+ - **SPP-R300** - 3" mobile printer (80mm)
1202
+ - ✅ **SPP-R400** - 4" mobile printer (112mm)
1203
+ - ✅ **SRP-275III** - 3" desktop printer (80mm)
1204
+ - **SRP-350III** - 3" desktop printer (80mm)
1205
+ - ✅ **SRP-352III** - 3" desktop printer (80mm)
1206
+
1207
+ #### Compatible Models
1208
+
1209
+ The following Bixolon models should work but have not been tested:
1210
+
1211
+ - SPP-R210, SPP-R220, SPP-R310, SPP-R410
1212
+ - SRP-330II, SRP-350plusIII, SRP-380
1213
+ - XD3-40d, XD5-40d
1214
+ - XT5-40, XT5-43
1215
+
1216
+ #### Connectivity Support
1217
+
1218
+ | Connection Type | Status | Notes |
1219
+ |----------------|--------|-------|
1220
+ | **Bluetooth** | ✅ Fully supported | Most common for mobile printers |
1221
+ | **WiFi** | ✅ Supported | For network-connected printers |
1222
+ | **USB** | ⚠️ Limited | Requires USB OTG on Android |
1223
+
1224
+ ### Adding Support for Other Brands
1225
+
1226
+ The architecture supports adding other printer brands. To add support:
1227
+
1228
+ 1. Implement the `IPrinter` interface in a new adapter
1229
+ 2. Integrate the vendor's SDK (Zebra, Epson, Star, etc.)
1230
+ 3. Register the adapter in the SDK entry point
1231
+
1232
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development guidelines.
1233
+
1234
+ ---
1235
+
1236
+ ## 🛠️ Troubleshooting
1030
1237
 
1031
1238
  ### Connection Issues
1032
1239
 
1033
1240
  **Problem**: Connection fails or times out
1034
1241
 
1035
1242
  **Solutions**:
1243
+ 1. Verify Bluetooth is enabled on the device
1244
+ 2. Check device is paired: `bluetooth.getPairedDevices()`
1245
+ 3. Ensure printer is powered on and in range (< 10 meters)
1246
+ 4. Try increasing timeout: `connectBluetooth(address, 60000)` (60 seconds)
1247
+ 5. Restart printer and retry connection
1036
1248
 
1037
- 1. Verify Bluetooth is enabled: `await Printer.bluetooth.isEnabled()`
1038
- 2. Check device is paired: `await Printer.bluetooth.getPairedDevices()`
1039
- 3. Ensure printer is powered on and in range
1040
- 4. Try reconnecting: `await Printer.connection.connect(address)`
1249
+ **Example:**
1250
+ ```typescript
1251
+ try {
1252
+ await connection.connectBluetooth(address, 60000);
1253
+ } catch (error) {
1254
+ console.error('Connection failed:', error);
1255
+ // Try restarting printer
1256
+ }
1257
+ ```
1041
1258
 
1042
1259
  ### Print Failures
1043
1260
 
1044
1261
  **Problem**: Print command succeeds but nothing prints
1045
1262
 
1046
1263
  **Solutions**:
1264
+ 1. Check printer status:
1265
+ ```typescript
1266
+ const status = await connection.getStatus();
1267
+ if (!status.hasPaper) console.error('No paper!');
1268
+ if (status.isCoverOpen) console.error('Cover open!');
1269
+ ```
1270
+ 2. Verify connection: `connection.isConnected()`
1271
+ 3. Check media configuration matches paper type
1272
+ 4. Ensure printer is not in an error state
1273
+
1274
+ ### Paper Not Feeding
1275
+
1276
+ **Problem**: Paper doesn't feed after printing
1277
+
1278
+ **Solutions**:
1279
+ 1. Add space lines at the end of receipt:
1280
+ ```typescript
1281
+ footer: [
1282
+ // ... other lines
1283
+ { type: 'space', lines: 3 } // Add extra space
1284
+ ]
1285
+ ```
1286
+ 2. Configure auto-cutter:
1287
+ ```typescript
1288
+ await config.set({
1289
+ autoCutter: { enabled: true, fullCut: true }
1290
+ });
1291
+ ```
1292
+
1293
+ ### Bluetooth Permissions Denied
1294
+
1295
+ **Problem**: "Permission denied" errors
1296
+
1297
+ **Solution**: Ensure permissions are declared in `app.json` and granted at runtime. Android 12+ requires runtime permission grants.
1298
+
1299
+ ```json
1300
+ {
1301
+ "expo": {
1302
+ "android": {
1303
+ "permissions": [
1304
+ "android.permission.BLUETOOTH",
1305
+ "android.permission.BLUETOOTH_ADMIN",
1306
+ "android.permission.BLUETOOTH_SCAN",
1307
+ "android.permission.BLUETOOTH_CONNECT"
1308
+ ]
1309
+ }
1310
+ }
1311
+ }
1312
+ ```
1313
+
1314
+ ### Image/QR Not Printing
1315
+
1316
+ **Problem**: Images or QR codes don't appear
1317
+
1318
+ **Solutions**:
1319
+ 1. Check base64 data is valid
1320
+ 2. Ensure alignment is correct
1321
+ 3. Try different QR size (1-10)
1322
+ 4. Verify printer supports graphics
1323
+
1324
+ ### Module Not Found Error
1325
+
1326
+ **Problem**: `Module "SincproPrinter" not found`
1047
1327
 
1048
- 1. Check printer status (paper, errors)
1049
- 2. Verify connection: `Printer.connection.isConnected()`
1050
- 3. Try smaller test print first
1051
- 4. Check printer-specific requirements (media config)
1328
+ **Solutions**:
1329
+ 1. Rebuild native modules:
1330
+ ```bash
1331
+ npx expo prebuild --clean
1332
+ npx expo run:android
1333
+ ```
1334
+ 2. Clear cache:
1335
+ ```bash
1336
+ npm start -- --clear
1337
+ ```
1052
1338
 
1053
1339
  ---
1054
1340
 
1055
1341
  ## 📖 Resources
1056
1342
 
1057
- - **Architecture**: [ARCHITECTURE.md](ARCHITECTURE.md) - Detailed architecture guide
1058
- - **Contributing**: [CONTRIBUTING.md](CONTRIBUTING.md) - Development guidelines
1059
- - **Copilot**: [.github/copilot-instructions.md](.github/copilot-instructions.md) - AI coding assistant rules
1343
+ - **Package**: [NPM Package](https://www.npmjs.com/package/@sincpro/printer-expo)
1344
+ - **Repository**: [GitHub](https://github.com/Sincpro-SRL/sincpro_printer_expo)
1345
+ - **Architecture**: [ARCHITECTURE.md](./ARCHITECTURE.md) - Detailed architecture guide
1346
+ - **Contributing**: [CONTRIBUTING.md](./CONTRIBUTING.md) - Development guidelines
1060
1347
  - **Expo Modules**: [Official Documentation](https://docs.expo.dev/modules/overview/)
1061
- - **Bixolon SDK**: [Official Documentation](https://www.bixolon.com/)
1348
+ - **Bixolon**: [Official Website](https://www.bixolon.com/)
1062
1349
 
1063
1350
  ---
1064
1351
 
1065
1352
  ## 🤝 Contributing
1066
1353
 
1067
- We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
1354
+ We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for:
1355
+
1356
+ - Development setup and environment
1357
+ - Code standards (ktlint, Prettier, ESLint)
1358
+ - Architecture guidelines and patterns
1359
+ - Git workflow and branch naming
1360
+ - Pull request process and review
1361
+ - Testing requirements
1362
+
1363
+ ### Quick Start for Contributors
1068
1364
 
1069
- - Development setup
1070
- - Code standards (ktlint, Prettier)
1071
- - Architecture guidelines
1072
- - Git workflow
1073
- - Pull request process
1365
+ ```bash
1366
+ # Clone the repository
1367
+ git clone https://github.com/Sincpro-SRL/sincpro_printer_expo.git
1368
+ cd sincpro_printer_expo
1369
+
1370
+ # Install dependencies
1371
+ npm install
1372
+
1373
+ # Build TypeScript
1374
+ npm run build
1375
+
1376
+ # Format code
1377
+ npm run format
1378
+ npm run format:kotlin
1379
+
1380
+ # Lint code
1381
+ npm run lint
1382
+ npm run lint:kotlin
1383
+ ```
1074
1384
 
1075
1385
  ---
1076
1386
 
1077
1387
  ## 📄 License
1078
1388
 
1079
- MIT License - see [LICENSE](LICENSE) file for details.
1389
+ MIT License - see [LICENSE](./LICENSE) file for details.
1390
+
1391
+ Copyright (c) 2024 Sincpro SRL
1080
1392
 
1081
1393
  ---
1082
1394
 
1083
1395
  ## 🙏 Acknowledgments
1084
1396
 
1085
- - Bixolon for the official printer SDK
1086
- - Expo team for the Modules API
1087
- - Contributors and testers
1397
+ - **Bixolon** for the official printer SDK
1398
+ - **Expo team** for the Modules API
1399
+ - **Contributors** and testers who helped improve this module
1400
+ - **Open source community** for inspiration and support
1401
+
1402
+ ---
1403
+
1404
+ ## 📞 Support
1405
+
1406
+ - **Issues**: [GitHub Issues](https://github.com/Sincpro-SRL/sincpro_printer_expo/issues)
1407
+ - **Discussions**: [GitHub Discussions](https://github.com/Sincpro-SRL/sincpro_printer_expo/discussions)
1408
+ - **Email**: support@sincpro.com
1088
1409
 
1089
1410
  ---
1090
1411