completed intent code
This commit is contained in:
parent
26d310b278
commit
4191458e15
115
README.md
115
README.md
|
|
@ -29,9 +29,13 @@ npx cap sync
|
|||
* [`requestPermissions()`](#requestpermissions)
|
||||
* [`scanBluetooth()`](#scanbluetooth)
|
||||
* [`scanUsb()`](#scanusb)
|
||||
* [`connectUsb()`](#connectusb)
|
||||
* [`scanNetwork()`](#scannetwork)
|
||||
* [`printBluetooth(...)`](#printbluetooth)
|
||||
* [`printNetwork(...)`](#printnetwork)
|
||||
* [`printUsb(...)`](#printusb)
|
||||
* [`printIntent(...)`](#printintent)
|
||||
* [`checkPrinterStatus(...)`](#checkprinterstatus)
|
||||
* [Interfaces](#interfaces)
|
||||
|
||||
</docgen-index>
|
||||
|
|
@ -76,6 +80,32 @@ Scan connected USB devices
|
|||
--------------------
|
||||
|
||||
|
||||
### connectUsb()
|
||||
|
||||
```typescript
|
||||
connectUsb() => Promise<UsbConnectionResult>
|
||||
```
|
||||
|
||||
Connect to USB printer
|
||||
|
||||
**Returns:** <code>Promise<<a href="#usbconnectionresult">UsbConnectionResult</a>></code>
|
||||
|
||||
--------------------
|
||||
|
||||
|
||||
### scanNetwork()
|
||||
|
||||
```typescript
|
||||
scanNetwork() => Promise<{ devices: NetworkPrinter[]; }>
|
||||
```
|
||||
|
||||
Scan Wi-Fi / LAN printers
|
||||
|
||||
**Returns:** <code>Promise<{ devices: NetworkPrinter[]; }></code>
|
||||
|
||||
--------------------
|
||||
|
||||
|
||||
### printBluetooth(...)
|
||||
|
||||
```typescript
|
||||
|
|
@ -127,6 +157,36 @@ Print via USB OTG
|
|||
--------------------
|
||||
|
||||
|
||||
### printIntent(...)
|
||||
|
||||
```typescript
|
||||
printIntent(options: PrintIntentOptions) => Promise<void>
|
||||
```
|
||||
|
||||
| Param | Type |
|
||||
| ------------- | ----------------------------------------------------------------- |
|
||||
| **`options`** | <code><a href="#printintentoptions">PrintIntentOptions</a></code> |
|
||||
|
||||
--------------------
|
||||
|
||||
|
||||
### checkPrinterStatus(...)
|
||||
|
||||
```typescript
|
||||
checkPrinterStatus(options: PrinterStatusOptions) => Promise<PrinterStatusResult>
|
||||
```
|
||||
|
||||
Check printer online/offline status
|
||||
|
||||
| Param | Type |
|
||||
| ------------- | --------------------------------------------------------------------- |
|
||||
| **`options`** | <code><a href="#printerstatusoptions">PrinterStatusOptions</a></code> |
|
||||
|
||||
**Returns:** <code>Promise<<a href="#printerstatusresult">PrinterStatusResult</a>></code>
|
||||
|
||||
--------------------
|
||||
|
||||
|
||||
### Interfaces
|
||||
|
||||
|
||||
|
|
@ -146,6 +206,24 @@ Print via USB OTG
|
|||
| **`address`** | <code>string</code> |
|
||||
|
||||
|
||||
#### UsbConnectionResult
|
||||
|
||||
| Prop | Type |
|
||||
| --------------- | -------------------- |
|
||||
| **`name`** | <code>string</code> |
|
||||
| **`address`** | <code>string</code> |
|
||||
| **`connected`** | <code>boolean</code> |
|
||||
|
||||
|
||||
#### NetworkPrinter
|
||||
|
||||
| Prop | Type |
|
||||
| ------------- | ------------------- |
|
||||
| **`name`** | <code>string</code> |
|
||||
| **`address`** | <code>string</code> |
|
||||
| **`port`** | <code>number</code> |
|
||||
|
||||
|
||||
#### BluetoothPrintOptions
|
||||
|
||||
| Prop | Type |
|
||||
|
|
@ -170,4 +248,41 @@ Print via USB OTG
|
|||
| **`data`** | <code>number[]</code> |
|
||||
| **`deviceId`** | <code>string</code> |
|
||||
|
||||
|
||||
#### PrintIntentOptions
|
||||
|
||||
| Prop | Type |
|
||||
| ----------------------- | ------------------- |
|
||||
| **`receiptText`** | <code>string</code> |
|
||||
| **`estimateText`** | <code>string</code> |
|
||||
| **`logoImage`** | <code>string</code> |
|
||||
| **`tokenDataSales`** | <code>any[]</code> |
|
||||
| **`tokenDataEstimate`** | <code>any[]</code> |
|
||||
| **`orderId`** | <code>string</code> |
|
||||
| **`estimateOrderId`** | <code>string</code> |
|
||||
| **`footerPrint`** | <code>string</code> |
|
||||
| **`upiId`** | <code>string</code> |
|
||||
| **`totalAmount`** | <code>string</code> |
|
||||
| **`estimateAmount`** | <code>string</code> |
|
||||
|
||||
|
||||
#### PrinterStatusResult
|
||||
|
||||
| Prop | Type |
|
||||
| --------------- | ---------------------------------- |
|
||||
| **`connected`** | <code>boolean</code> |
|
||||
| **`status`** | <code>'online' \| 'offline'</code> |
|
||||
| **`type`** | <code>string</code> |
|
||||
| **`address`** | <code>string</code> |
|
||||
| **`name`** | <code>string</code> |
|
||||
| **`message`** | <code>string</code> |
|
||||
|
||||
|
||||
#### PrinterStatusOptions
|
||||
|
||||
| Prop | Type |
|
||||
| ------------- | -------------------------------------------- |
|
||||
| **`type`** | <code>'USB' \| 'Bluetooth' \| 'Wi-Fi'</code> |
|
||||
| **`address`** | <code>string</code> |
|
||||
|
||||
</docgen-api>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ repositories {
|
|||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(':capacitor-android')
|
||||
implementation 'com.google.code.gson:gson:2.14.0'
|
||||
implementation 'com.github.dantsu:escpos-thermalprinter-android:3.4.0'
|
||||
implementation "com.google.zxing:core:3.5.4"
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,25 @@
|
|||
package com.pozo.printer;
|
||||
|
||||
import android.Manifest;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.pozo.printer.helper.LogoCache;
|
||||
import com.pozo.printer.model.TokenItem;
|
||||
import com.pozo.printer.utils.EscPosUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
|
@ -13,18 +27,43 @@ import java.util.UUID;
|
|||
|
||||
public class BluetoothPrinter {
|
||||
|
||||
private static final UUID SPP_UUID =
|
||||
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
private static byte[] CUT_PAPER = new byte[] { 0x1D, 0x56, 0x00 };
|
||||
|
||||
public static List<JSObject> scanDevices(Context context) {
|
||||
private static final UUID PRINTER_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
public static List<JSObject> scanDevices(Context context) throws Exception {
|
||||
List<JSObject> result = new ArrayList<>();
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (adapter == null || !adapter.isEnabled()) return result;
|
||||
|
||||
if (adapter == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.BLUETOOTH_CONNECT)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
throw new Exception("Bluetooth permission not granted");
|
||||
}
|
||||
|
||||
Set<BluetoothDevice> paired = adapter.getBondedDevices();
|
||||
|
||||
for (BluetoothDevice device : paired) {
|
||||
|
||||
JSObject obj = new JSObject();
|
||||
obj.put("name", device.getName() != null ? device.getName() : "Unknown");
|
||||
obj.put("address", device.getAddress());
|
||||
|
||||
obj.put(
|
||||
"name",
|
||||
device.getName() != null
|
||||
? device.getName()
|
||||
: "Unknown");
|
||||
|
||||
obj.put(
|
||||
"address",
|
||||
device.getAddress());
|
||||
|
||||
result.add(obj);
|
||||
}
|
||||
return result;
|
||||
|
|
@ -47,7 +86,7 @@ public class BluetoothPrinter {
|
|||
while (attempt < maxRetries) {
|
||||
BluetoothSocket socket = null;
|
||||
try {
|
||||
socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
|
||||
socket = device.createRfcommSocketToServiceRecord(PRINTER_UUID);
|
||||
socket.connect();
|
||||
|
||||
OutputStream out = socket.getOutputStream();
|
||||
|
|
@ -67,18 +106,376 @@ public class BluetoothPrinter {
|
|||
lastError = e;
|
||||
attempt++;
|
||||
if (socket != null) {
|
||||
try { socket.close(); } catch (Exception ignored) {}
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (attempt < maxRetries) {
|
||||
Thread.sleep(1000); // wait before retry
|
||||
}
|
||||
} finally {
|
||||
if (socket != null) {
|
||||
try { socket.close(); } catch (Exception ignored) {}
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Failed after " + maxRetries + " attempts: " + lastError.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void printBluetooth(
|
||||
Context context,
|
||||
String macAddress,
|
||||
String receiptText,
|
||||
String estimateText,
|
||||
List<List<TokenItem>> salesTokens,
|
||||
List<List<TokenItem>> estimateTokens,
|
||||
String logoImage,
|
||||
String totalAmount,
|
||||
String estimateAmount,
|
||||
String footerPrint,
|
||||
String upiId,
|
||||
String orderId,
|
||||
String estimateOrderId,
|
||||
String salesId,
|
||||
String salesIdEst) throws Exception {
|
||||
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
BluetoothDevice device = adapter.getRemoteDevice(macAddress);
|
||||
|
||||
BluetoothSocket socket = connectToDevice(context, device);
|
||||
|
||||
OutputStream outputStream = socket.getOutputStream();
|
||||
|
||||
long printStartTime = System.currentTimeMillis();
|
||||
|
||||
Log.d("PRINT_TIME", "Printing Started");
|
||||
|
||||
if (logoImage != null && !logoImage.isEmpty()) {
|
||||
|
||||
byte[] imageData = LogoCache.getBluetoothLogo(logoImage);
|
||||
|
||||
if (imageData != null) {
|
||||
|
||||
writeInChunks(outputStream, imageData);
|
||||
|
||||
Thread.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (receiptText != null && !receiptText.isEmpty()) {
|
||||
|
||||
byte[] parsedText = parseFormattedTextToEscPos(receiptText, footerPrint);
|
||||
|
||||
writeInChunks(outputStream, parsedText);
|
||||
|
||||
Thread.sleep(2);
|
||||
|
||||
byte[] qrData = generateQRCodeByte(upiId, totalAmount, footerPrint);
|
||||
|
||||
writeInChunks(outputStream, qrData);
|
||||
|
||||
outputStream.write(CUT_PAPER);
|
||||
|
||||
}
|
||||
|
||||
if (estimateText != null && !estimateText.isEmpty()) {
|
||||
|
||||
byte[] parsedText = parseFormattedTextToEscPos(estimateText, footerPrint);
|
||||
|
||||
writeInChunks(outputStream, parsedText);
|
||||
|
||||
Thread.sleep(2);
|
||||
|
||||
byte[] qrData = generateQRCodeByte(upiId, estimateAmount, footerPrint);
|
||||
|
||||
writeInChunks(outputStream, qrData);
|
||||
|
||||
outputStream.write(CUT_PAPER);
|
||||
|
||||
}
|
||||
|
||||
if (salesTokens != null && !salesTokens.isEmpty()) {
|
||||
|
||||
printBluetoothTokens(
|
||||
outputStream,
|
||||
salesTokens,
|
||||
orderId,
|
||||
"Sales",
|
||||
salesId);
|
||||
}
|
||||
|
||||
if (estimateTokens != null && !estimateTokens.isEmpty()) {
|
||||
|
||||
printBluetoothTokens(
|
||||
outputStream,
|
||||
estimateTokens,
|
||||
estimateOrderId,
|
||||
"Estimate",
|
||||
salesIdEst);
|
||||
}
|
||||
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
socket.close();
|
||||
|
||||
long printEndTime = System.currentTimeMillis();
|
||||
|
||||
Log.d("PRINT_TIME", "Printing Completed");
|
||||
|
||||
Log.d("PRINT_TIME",
|
||||
"Total Print Time = "
|
||||
+ (printEndTime - printStartTime)
|
||||
+ " ms");
|
||||
}
|
||||
|
||||
private static BluetoothSocket connectToDevice(
|
||||
Context context,
|
||||
BluetoothDevice device) {
|
||||
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (bluetoothAdapter == null) {
|
||||
Log.e("PrinterLog", "Bluetooth not supported");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Android 12+ permission check
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
throw new SecurityException("BLUETOOTH_CONNECT permission not granted");
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
}
|
||||
|
||||
Log.d("BT_TIME", "Before create socket");
|
||||
|
||||
BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(PRINTER_UUID);
|
||||
|
||||
Log.d("BT_TIME", "Before connect");
|
||||
|
||||
socket.connect();
|
||||
|
||||
Log.d("BT_TIME", "After connect");
|
||||
|
||||
Log.d("PrinterLog", "Bluetooth Connected Successfully");
|
||||
|
||||
return socket;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
|
||||
Log.e("PrinterLog", "Bluetooth permission missing", e);
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
Log.e("PrinterLog", "Bluetooth connection failed", e);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.e("PrinterLog", "Unexpected Bluetooth error", e);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void printBluetoothTokens(OutputStream outputStream, List<List<TokenItem>> tokensList,
|
||||
String orderId, String SalesorEstimate,
|
||||
String salesId) throws Exception {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
for (List<TokenItem> token : tokensList) {
|
||||
|
||||
buffer.reset();
|
||||
|
||||
String tokenText = EscPosUtils.buildTokenDesign(token, orderId, salesId, SalesorEstimate);
|
||||
|
||||
byte[] tokenBytes = parseFormattedTextToEscPos(tokenText, "");
|
||||
|
||||
writeInChunks(outputStream, tokenBytes);
|
||||
|
||||
outputStream.write(new byte[]{0x0A, 0x0A, 0x0A});
|
||||
|
||||
outputStream.write(CUT_PAPER);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static byte[] parseFormattedTextToEscPos(String text, String footerPrint) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
String[] lines = text.split("\n");
|
||||
|
||||
buffer.write(new byte[] { 0x1B, 0x40 }); /* Reset the printer */
|
||||
|
||||
for (String line : lines) {
|
||||
// Skip empty lines
|
||||
if (line.trim().isEmpty()) {
|
||||
buffer.write("\n".getBytes());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip HTML comments
|
||||
if (line.trim().startsWith("<!--") || line.trim().contains("<!--")) {
|
||||
continue;
|
||||
}
|
||||
if (line.contains("[L]") && line.contains("[R]")) {
|
||||
|
||||
String left = line.substring(line.indexOf("[L]") + 3, line.indexOf("[R]"));
|
||||
String right = line.substring(line.indexOf("[R]") + 3);
|
||||
|
||||
left = left.replaceAll("<[^>]*>", "").trim();
|
||||
right = right.replaceAll("<[^>]*>", "").trim();
|
||||
|
||||
String formatted = String.format("%-24s%24s", left, right);
|
||||
|
||||
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
|
||||
buffer.write(formatted.getBytes(StandardCharsets.UTF_8));
|
||||
buffer.write(0x0A);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check and apply alignment
|
||||
byte alignment = 0x00; // Default left
|
||||
if (line.startsWith("[C]")) {
|
||||
alignment = 0x01; // Center
|
||||
line = line.substring(3);
|
||||
} else if (line.startsWith("[R]")) {
|
||||
alignment = 0x02; // Right
|
||||
line = line.substring(3);
|
||||
} else if (line.startsWith("[L]")) {
|
||||
alignment = 0x00; // Left
|
||||
line = line.substring(3);
|
||||
}
|
||||
buffer.write(new byte[] { 0x1B, 0x61, alignment }); // ESC a n - Set alignment
|
||||
|
||||
// Check for formatting tags
|
||||
boolean isBold = line.contains("<b>");
|
||||
boolean isUnderline = line.contains("<u>");
|
||||
boolean isBigFont = line.contains("<font size='big'>") || line.contains("<font size=\"big\">");
|
||||
boolean isNormalFont = line.contains("<font size='normal'>") || line.contains("<font size=\"normal\">");
|
||||
|
||||
// Apply bold
|
||||
if (isBold) {
|
||||
buffer.write(new byte[] { 0x1B, 0x45, 0x01 }); // ESC E 1 - Bold ON
|
||||
}
|
||||
|
||||
// Apply underline
|
||||
if (isUnderline) {
|
||||
buffer.write(new byte[] { 0x1B, 0x2D, 0x01 }); // ESC - 1 - Underline ON
|
||||
}
|
||||
|
||||
// Apply font size
|
||||
if (isBigFont) {
|
||||
buffer.write(new byte[] { 0x1D, 0x21, 0x11 }); // GS ! 17 - Double width and height
|
||||
} else if (isNormalFont) {
|
||||
buffer.write(new byte[] { 0x1D, 0x21, 0x00 }); // GS ! 0 - Normal size
|
||||
}
|
||||
|
||||
/* remove the HTML line And the tags */
|
||||
String cleanLine = line
|
||||
.replaceAll("<!--.*?-->", "")
|
||||
.replaceAll("<b>|</b>", "")
|
||||
.replaceAll("<u>|</u>", "")
|
||||
.replaceAll("<font[^>]*>|</font>", "")
|
||||
.replace("[L]", "")
|
||||
.replace("[R]", "")
|
||||
.replace("[C]", "")
|
||||
.replace("–", "-")
|
||||
.trim();
|
||||
|
||||
// Only write if there's content after cleaning
|
||||
if (!cleanLine.isEmpty()) {
|
||||
buffer.write(cleanLine.getBytes(StandardCharsets.UTF_8));
|
||||
buffer.write(0x0A);
|
||||
|
||||
buffer.write(new byte[] { 0x1B, 0x45, 0x00 }); // Bold OFF /*This one For reset a bold. bold off*/
|
||||
buffer.write(new byte[] { 0x1B, 0x2D, 0x00 }); // Underline OFF /* This one for reset the Underline
|
||||
// to off*/
|
||||
buffer.write(new byte[] { 0x1D, 0x21, 0x00 }); // Normal size /*This One for normal size*/
|
||||
}
|
||||
}
|
||||
|
||||
// Reset to left alignment at end
|
||||
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("ParseError", "Error parsing formatted text", e);
|
||||
}
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
private static byte[] generateQRCodeByte(String upiId, String amount, String footerPrint) {
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
|
||||
if (upiId != null && !upiId.isEmpty()) {
|
||||
|
||||
buffer.write(new byte[] { 0x1B, 0x61, 0x01 });
|
||||
|
||||
String upiString = "upi://pay?pa=" + upiId + "&pn=Merchant&am=" + amount + "&cu=INR";
|
||||
|
||||
buffer.write(EscPosUtils.generateQRCodeEscPos(upiString));
|
||||
buffer.write("Scan To Pay\n".getBytes(StandardCharsets.UTF_8));
|
||||
buffer.write(("Rs: " + amount + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
|
||||
|
||||
}
|
||||
|
||||
buffer.write(EscPosUtils.generateFooter(footerPrint));
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("QR", "QR Error", e);
|
||||
}
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
private static void writeInChunks(OutputStream out, byte[] data) throws Exception {
|
||||
|
||||
int offset = 0;
|
||||
|
||||
int chunkSize = 512;
|
||||
|
||||
while (offset < data.length) {
|
||||
|
||||
int len = Math.min(chunkSize, data.length - offset);
|
||||
|
||||
out.write(data, offset, len);
|
||||
|
||||
out.flush();
|
||||
|
||||
Log.d("BT_CHUNK", "Sent: " + offset + " to " + (offset + len));
|
||||
|
||||
offset += len;
|
||||
|
||||
Thread.sleep(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,346 @@
|
|||
package com.pozo.printer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dantsu.escposprinter.EscPosPrinter;
|
||||
import com.dantsu.escposprinter.connection.tcp.TcpConnection;
|
||||
import com.getcapacitor.JSArray;
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.pozo.printer.helper.ReceiptBuilder;
|
||||
import com.pozo.printer.model.TokenItem;
|
||||
import com.pozo.printer.utils.EscPosUtils;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class NetworkPrinter {
|
||||
|
||||
public static void print(String host, int port, byte[] data) throws Exception {
|
||||
Socket socket = new Socket(host, port);
|
||||
socket.setSoTimeout(10000);
|
||||
try {
|
||||
OutputStream out = socket.getOutputStream();
|
||||
out.write(data);
|
||||
out.flush();
|
||||
} finally {
|
||||
socket.close();
|
||||
private static final String TAG = "NETWORK_PRINTER";
|
||||
private static final int PORT = 9100;
|
||||
private static final int TIMEOUT = 1000;
|
||||
private static final int THREAD_COUNT = 20;
|
||||
private static final int RETRY_COUNT = 2;
|
||||
|
||||
/**
|
||||
* Scan current subnet for ESC/POS printers.
|
||||
*/
|
||||
public static JSArray scan(Context context) throws Exception {
|
||||
|
||||
JSArray printers = new JSArray();
|
||||
|
||||
WifiManager wifiManager =
|
||||
(WifiManager) context.getApplicationContext()
|
||||
.getSystemService(Context.WIFI_SERVICE);
|
||||
|
||||
if (wifiManager == null) {
|
||||
throw new Exception("WifiManager unavailable");
|
||||
}
|
||||
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
|
||||
if (wifiInfo == null) {
|
||||
throw new Exception("Not connected to Wi-Fi");
|
||||
}
|
||||
|
||||
String myIp = Formatter.formatIpAddress(wifiInfo.getIpAddress());
|
||||
|
||||
if (myIp == null || myIp.equals("0.0.0.0")) {
|
||||
throw new Exception("Invalid IP Address");
|
||||
}
|
||||
|
||||
Log.d(TAG, "My IP : " + myIp);
|
||||
|
||||
String subnet =
|
||||
myIp.substring(0, myIp.lastIndexOf('.') + 1);
|
||||
|
||||
Log.d(TAG, "Subnet : " + subnet);
|
||||
|
||||
ExecutorService executor =
|
||||
Executors.newFixedThreadPool(THREAD_COUNT);
|
||||
|
||||
CountDownLatch latch =
|
||||
new CountDownLatch(254);
|
||||
|
||||
Set<String> foundHosts =
|
||||
Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
for (int i = 1; i <= 254; i++) {
|
||||
|
||||
final String host = subnet + i;
|
||||
|
||||
executor.execute(() -> {
|
||||
|
||||
try {
|
||||
|
||||
boolean connected = false;
|
||||
|
||||
for (int retry = 0; retry < RETRY_COUNT; retry++) {
|
||||
|
||||
Socket socket = new Socket();
|
||||
|
||||
try {
|
||||
|
||||
socket.connect(
|
||||
new InetSocketAddress(host, PORT),
|
||||
TIMEOUT);
|
||||
|
||||
connected = true;
|
||||
|
||||
break;
|
||||
|
||||
} catch (Exception ignored) {
|
||||
|
||||
} finally {
|
||||
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
}
|
||||
|
||||
if (connected && foundHosts.add(host)) {
|
||||
|
||||
Log.d(TAG, "Printer Found : " + host);
|
||||
|
||||
synchronized (printers) {
|
||||
|
||||
JSObject obj = new JSObject();
|
||||
|
||||
obj.put("name", "Network Printer");
|
||||
obj.put("address", host);
|
||||
obj.put("port", PORT);
|
||||
|
||||
printers.put(obj);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception ignored) {
|
||||
|
||||
} finally {
|
||||
|
||||
latch.countDown();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
latch.await();
|
||||
|
||||
executor.shutdown();
|
||||
|
||||
Log.d(TAG, "Total Printers : " + printers.length());
|
||||
|
||||
return printers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print raw ESC/POS data.
|
||||
*/
|
||||
public static void print(
|
||||
String host,
|
||||
int port,
|
||||
byte[] data) throws Exception {
|
||||
|
||||
Socket socket = new Socket();
|
||||
|
||||
socket.connect(
|
||||
new InetSocketAddress(host, port),
|
||||
5000);
|
||||
|
||||
socket.setSoTimeout(10000);
|
||||
|
||||
try {
|
||||
|
||||
OutputStream out =
|
||||
socket.getOutputStream();
|
||||
|
||||
out.write(data);
|
||||
|
||||
out.flush();
|
||||
|
||||
} finally {
|
||||
|
||||
socket.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a printer is reachable.
|
||||
*/
|
||||
public static boolean isReachable(
|
||||
String host,
|
||||
int port) {
|
||||
|
||||
try {
|
||||
|
||||
Socket socket = new Socket();
|
||||
|
||||
socket.connect(
|
||||
new InetSocketAddress(host, port),
|
||||
500);
|
||||
|
||||
socket.close();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void printNetwork(
|
||||
String host,
|
||||
int port,
|
||||
String receiptText,
|
||||
String estimateText,
|
||||
List<List<TokenItem>> salesTokens,
|
||||
List<List<TokenItem>> estimateTokens,
|
||||
String logoImage,
|
||||
String totalAmount,
|
||||
String estimateAmount,
|
||||
String footerPrint,
|
||||
String upiId,
|
||||
String orderId,
|
||||
String estimateOrderId,
|
||||
String salesId,
|
||||
String salesIdEst) throws Exception {
|
||||
|
||||
Log.d("NETWORK_PRINT", "========== START ==========");
|
||||
Log.d("NETWORK_PRINT", "Host : " + host);
|
||||
Log.d("NETWORK_PRINT", "Port : " + port);
|
||||
|
||||
try {
|
||||
|
||||
Log.d("NETWORK_PRINT", "Creating TcpConnection...");
|
||||
|
||||
TcpConnection connection = new TcpConnection(host, port, 5000);
|
||||
|
||||
Log.d("NETWORK_PRINT", "Creating EscPosPrinter...");
|
||||
|
||||
EscPosPrinter printer = new EscPosPrinter(
|
||||
connection,
|
||||
203,
|
||||
80f,
|
||||
45);
|
||||
|
||||
Log.d("NETWORK_PRINT", "Printer Connected");
|
||||
|
||||
// ---------------- Receipt ----------------
|
||||
|
||||
if (receiptText != null && !receiptText.isEmpty()) {
|
||||
|
||||
Log.d("NETWORK_PRINT", "Building Receipt...");
|
||||
|
||||
String receipt = ReceiptBuilder.buildReceipt(
|
||||
printer,
|
||||
receiptText,
|
||||
logoImage,
|
||||
upiId,
|
||||
totalAmount,
|
||||
footerPrint);
|
||||
|
||||
Log.d("NETWORK_PRINT", "Receipt Length : " + receipt.length());
|
||||
|
||||
Log.d("NETWORK_PRINT", "Printing Receipt...");
|
||||
|
||||
printer.printFormattedTextAndCut(receipt);
|
||||
|
||||
Log.d("NETWORK_PRINT", "Receipt Printed");
|
||||
}
|
||||
|
||||
// ---------------- Estimate ----------------
|
||||
|
||||
if (estimateText != null && !estimateText.isEmpty()) {
|
||||
|
||||
Log.d("NETWORK_PRINT", "Building Estimate...");
|
||||
|
||||
String estimate = ReceiptBuilder.buildReceipt(
|
||||
printer,
|
||||
estimateText,
|
||||
logoImage,
|
||||
upiId,
|
||||
estimateAmount,
|
||||
footerPrint);
|
||||
|
||||
Log.d("NETWORK_PRINT", "Estimate Length : " + estimate.length());
|
||||
|
||||
Log.d("NETWORK_PRINT", "Printing Estimate...");
|
||||
|
||||
printer.printFormattedTextAndCut(estimate);
|
||||
|
||||
Log.d("NETWORK_PRINT", "Estimate Printed");
|
||||
}
|
||||
|
||||
// ---------------- Sales Tokens ----------------
|
||||
|
||||
if (salesTokens != null && !salesTokens.isEmpty()) {
|
||||
|
||||
Log.d("NETWORK_PRINT",
|
||||
"Printing Sales Tokens : " + salesTokens.size());
|
||||
|
||||
EscPosUtils.printUsbOrNetworkTokens(
|
||||
printer,
|
||||
salesTokens,
|
||||
orderId,
|
||||
salesId,
|
||||
"Sales");
|
||||
|
||||
Log.d("NETWORK_PRINT", "Sales Tokens Printed");
|
||||
}
|
||||
|
||||
// ---------------- Estimate Tokens ----------------
|
||||
|
||||
if (estimateTokens != null && !estimateTokens.isEmpty()) {
|
||||
|
||||
Log.d("NETWORK_PRINT",
|
||||
"Printing Estimate Tokens : " + estimateTokens.size());
|
||||
|
||||
EscPosUtils.printUsbOrNetworkTokens(
|
||||
printer,
|
||||
estimateTokens,
|
||||
estimateOrderId,
|
||||
salesIdEst,
|
||||
"Estimate");
|
||||
|
||||
Log.d("NETWORK_PRINT", "Estimate Tokens Printed");
|
||||
}
|
||||
|
||||
Log.d("NETWORK_PRINT", "Disconnecting Printer...");
|
||||
|
||||
printer.disconnectPrinter();
|
||||
|
||||
Log.d("NETWORK_PRINT", "Printer Disconnected");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.e("NETWORK_PRINT", "Print Failed", e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
Log.d("NETWORK_PRINT", "=========== END ===========");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,18 @@
|
|||
package com.pozo.printer;
|
||||
|
||||
import android.Manifest;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.dantsu.escposprinter.connection.bluetooth.BluetoothConnection;
|
||||
import com.getcapacitor.JSArray;
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.getcapacitor.PermissionState;
|
||||
|
|
@ -11,29 +22,32 @@ import com.getcapacitor.PluginMethod;
|
|||
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
import com.getcapacitor.annotation.Permission;
|
||||
import com.getcapacitor.annotation.PermissionCallback;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import com.pozo.printer.model.TokenItem;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@CapacitorPlugin(
|
||||
name = "Printer",
|
||||
permissions = {
|
||||
@Permission(
|
||||
strings = {
|
||||
@CapacitorPlugin(name = "Printer", permissions = {
|
||||
@Permission(strings = {
|
||||
Manifest.permission.BLUETOOTH,
|
||||
Manifest.permission.BLUETOOTH_ADMIN,
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
Manifest.permission.BLUETOOTH_SCAN
|
||||
},
|
||||
alias = "bluetooth"
|
||||
),
|
||||
@Permission(
|
||||
strings = {
|
||||
}, alias = "bluetooth"),
|
||||
@Permission(strings = {
|
||||
Manifest.permission.INTERNET,
|
||||
Manifest.permission.ACCESS_NETWORK_STATE
|
||||
},
|
||||
alias = "network"
|
||||
)
|
||||
}
|
||||
)
|
||||
}, alias = "network")
|
||||
})
|
||||
public class PrinterPlugin extends Plugin {
|
||||
|
||||
// ─── Request Permissions ────────────────────────────────────────
|
||||
|
|
@ -55,11 +69,9 @@ public class PrinterPlugin extends Plugin {
|
|||
|
||||
@PluginMethod
|
||||
public void scanBluetooth(PluginCall call) {
|
||||
if (
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
|
||||
getContext().checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
|
||||
getContext().checkSelfPermission(
|
||||
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissionForAlias("bluetooth", call, "bluetoothScanCallback");
|
||||
return;
|
||||
}
|
||||
|
|
@ -80,7 +92,8 @@ public class PrinterPlugin extends Plugin {
|
|||
List<JSObject> devices = BluetoothPrinter.scanDevices(getContext());
|
||||
JSObject result = new JSObject();
|
||||
JSArray arr = new JSArray();
|
||||
for (JSObject d : devices) arr.put(d);
|
||||
for (JSObject d : devices)
|
||||
arr.put(d);
|
||||
result.put("devices", arr);
|
||||
call.resolve(result);
|
||||
} catch (Exception e) {
|
||||
|
|
@ -92,17 +105,36 @@ public class PrinterPlugin extends Plugin {
|
|||
|
||||
@PluginMethod
|
||||
public void printBluetooth(PluginCall call) {
|
||||
if (
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
|
||||
getContext().checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
|
||||
getContext().checkSelfPermission(
|
||||
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissionForAlias("bluetooth", call, "bluetoothPrintCallback");
|
||||
return;
|
||||
}
|
||||
performBluetoothPrint(call);
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void scanNetwork(PluginCall call) {
|
||||
|
||||
new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
JSObject ret = new JSObject();
|
||||
ret.put("devices", NetworkPrinter.scan(getContext()));
|
||||
|
||||
call.resolve(ret);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
call.reject(e.getMessage(), e);
|
||||
|
||||
}
|
||||
|
||||
}).start();
|
||||
}
|
||||
|
||||
@PermissionCallback
|
||||
private void bluetoothPrintCallback(PluginCall call) {
|
||||
if (getPermissionState("bluetooth") == PermissionState.GRANTED) {
|
||||
|
|
@ -140,7 +172,8 @@ public class PrinterPlugin extends Plugin {
|
|||
List<JSObject> devices = UsbPrinter.scanDevices(getContext());
|
||||
JSObject result = new JSObject();
|
||||
JSArray arr = new JSArray();
|
||||
for (JSObject d : devices) arr.put(d);
|
||||
for (JSObject d : devices)
|
||||
arr.put(d);
|
||||
result.put("devices", arr);
|
||||
call.resolve(result);
|
||||
} catch (Exception e) {
|
||||
|
|
@ -148,6 +181,35 @@ public class PrinterPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── USB Connect ───────────────────────────────────────────────────
|
||||
|
||||
@PluginMethod
|
||||
public void connectUsb(PluginCall call) {
|
||||
|
||||
try {
|
||||
Log.d("USB_CONNECT" , "USB" + "connectUsb Triggered");
|
||||
UsbPrinter.connectUsb(
|
||||
getContext(),
|
||||
new UsbPrinter.ConnectCallback() {
|
||||
@Override
|
||||
public void onConnected(JSObject printer) {
|
||||
call.resolve(printer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
call.reject(message);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
call.reject(e.getMessage(), e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ─── USB Print ──────────────────────────────────────────────────
|
||||
|
||||
@PluginMethod
|
||||
|
|
@ -158,17 +220,28 @@ public class PrinterPlugin extends Plugin {
|
|||
call.reject("Missing data");
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
|
||||
byte[] data;
|
||||
try {
|
||||
byte[] data = jsArrayToBytes(dataArray);
|
||||
UsbPrinter.print(getContext(), data, deviceId);
|
||||
data = jsArrayToBytes(dataArray);
|
||||
} catch (Exception e) {
|
||||
call.reject("Invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
UsbPrinter.printWithPermission(getContext(), data, deviceId, new UsbPrinter.PrintCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
JSObject result = new JSObject();
|
||||
result.put("success", true);
|
||||
call.resolve(result);
|
||||
} catch (Exception e) {
|
||||
call.reject("USB print failed: " + e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
call.reject("USB print failed: " + message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Network Print ──────────────────────────────────────────────
|
||||
|
|
@ -204,4 +277,275 @@ public class PrinterPlugin extends Plugin {
|
|||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ─── Intent ─────────────────────────────────────────────────────
|
||||
|
||||
@PluginMethod
|
||||
public void printIntent(PluginCall call) {
|
||||
|
||||
String receiptText = call.getString("receiptText", "");
|
||||
String estimateText = call.getString("estimateText", "");
|
||||
String logoImage = call.getString("logoImage", "");
|
||||
|
||||
JSArray tokenDataSales = call.getArray("tokenDataSales");
|
||||
JSArray tokenDataEstimate = call.getArray("tokenDataEstimate");
|
||||
|
||||
String orderId = call.getString("orderId", "");
|
||||
String estimateOrderId = call.getString("estimateOrderId", "");
|
||||
String salesId = call.getString("salesIdNormal", "");
|
||||
String salesIdEst = call.getString("salesIdEst", "");
|
||||
String footerPrint = call.getString("footerPrint", "");
|
||||
String upiId = call.getString("upiId", "");
|
||||
String totalAmount = call.getString("totalAmount", "");
|
||||
String estimateAmount = call.getString("estimateAmount", "");
|
||||
|
||||
String macAddress = call.getString("macAddress", "");
|
||||
String usbDeviceId = call.getString("usbAddress", "");
|
||||
String wifiAddress = call.getString("wifiAddress", "");
|
||||
Log.d("AddressUI", "macAddress" + macAddress);
|
||||
Log.d("AddressUI", "usbAddress" + usbDeviceId);
|
||||
Log.d("AddressUI", "wifiAddress" + wifiAddress);
|
||||
Log.d("AddressUI", "totalAmount" + totalAmount);
|
||||
Log.d("PluginData", "receiptText = " + receiptText);
|
||||
Log.d("PluginData", "estimateText = " + estimateText);
|
||||
Log.d("PluginData", "orderId = " + orderId);
|
||||
Log.d("PluginData", "estimateOrderId = " + estimateOrderId);
|
||||
Gson gson = new Gson();
|
||||
|
||||
Type tokenType = new TypeToken<List<List<TokenItem>>>() {
|
||||
}.getType();
|
||||
|
||||
List<List<TokenItem>> salesTokens = new ArrayList<>();
|
||||
List<List<TokenItem>> estimateTokens = new ArrayList<>();
|
||||
|
||||
if (tokenDataSales != null) {
|
||||
|
||||
salesTokens = gson.fromJson(tokenDataSales.toString(), tokenType);
|
||||
Log.d("TokenDebug", "Sales Tokens Count: " + salesTokens.size());
|
||||
|
||||
}
|
||||
|
||||
Log.d("TokenDebug", "tokenDataEstimate: " + tokenDataEstimate);
|
||||
|
||||
if (tokenDataEstimate != null) {
|
||||
|
||||
estimateTokens = gson.fromJson(tokenDataEstimate.toString(), tokenType);
|
||||
Log.d("TokenDebug", "Estimate Tokens Count: " + estimateTokens.size());
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (macAddress != null && !macAddress.trim().isEmpty()) {
|
||||
|
||||
BluetoothPrinter.printBluetooth(
|
||||
getContext(),
|
||||
macAddress,
|
||||
receiptText,
|
||||
estimateText,
|
||||
salesTokens,
|
||||
estimateTokens,
|
||||
logoImage,
|
||||
totalAmount,
|
||||
estimateAmount,
|
||||
footerPrint,
|
||||
upiId,
|
||||
orderId,
|
||||
estimateOrderId,
|
||||
salesId,
|
||||
salesIdEst);
|
||||
|
||||
} else if (usbDeviceId != null && !usbDeviceId.trim().isEmpty()) {
|
||||
|
||||
UsbPrinter.printUsb(
|
||||
getContext(),
|
||||
usbDeviceId,
|
||||
receiptText,
|
||||
estimateText,
|
||||
logoImage,
|
||||
totalAmount,
|
||||
estimateAmount,
|
||||
footerPrint,
|
||||
upiId,
|
||||
salesTokens,
|
||||
estimateTokens,
|
||||
orderId,
|
||||
estimateOrderId,
|
||||
salesId,
|
||||
salesIdEst);
|
||||
|
||||
} else if (wifiAddress != null && !wifiAddress.trim().isEmpty()) {
|
||||
String[] parts = wifiAddress.split(":");
|
||||
String host = parts[0];
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
NetworkPrinter.printNetwork(host, port, receiptText, estimateText, salesTokens, estimateTokens,
|
||||
logoImage, totalAmount,
|
||||
estimateAmount, footerPrint, upiId, orderId, estimateOrderId, salesId, salesIdEst);
|
||||
} else {
|
||||
|
||||
call.reject("No printer selected");
|
||||
return;
|
||||
}
|
||||
|
||||
call.resolve();
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
call.reject(e.getMessage(), e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void checkPrinterStatus(PluginCall call) {
|
||||
|
||||
JSObject result = new JSObject();
|
||||
|
||||
try {
|
||||
|
||||
String type = call.getString("type", "");
|
||||
String address = call.getString("address", "");
|
||||
|
||||
if (type.isEmpty() || address.isEmpty()) {
|
||||
call.reject("Type and address are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// USB
|
||||
// --------------------------------------------------
|
||||
if ("USB".equalsIgnoreCase(type)) {
|
||||
|
||||
UsbManager usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
|
||||
|
||||
HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
|
||||
|
||||
for (UsbDevice device : devices.values()) {
|
||||
|
||||
if (String.valueOf(device.getDeviceId()).equals(address)) {
|
||||
|
||||
result.put("connected", true);
|
||||
result.put("status", "online");
|
||||
result.put("type", "USB");
|
||||
result.put("address", address);
|
||||
result.put("name", device.getProductName());
|
||||
|
||||
call.resolve(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
result.put("connected", false);
|
||||
result.put("status", "offline");
|
||||
result.put("type", "USB");
|
||||
result.put("address", address);
|
||||
|
||||
call.resolve(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Bluetooth
|
||||
// --------------------------------------------------
|
||||
if ("Bluetooth".equalsIgnoreCase(type)) {
|
||||
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (adapter == null) {
|
||||
result.put("connected", false);
|
||||
result.put("status", "offline");
|
||||
result.put("message", "Bluetooth not supported");
|
||||
call.resolve(result);
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothDevice device = adapter.getRemoteDevice(address);
|
||||
|
||||
try {
|
||||
|
||||
BluetoothConnection connection = new BluetoothConnection(device);
|
||||
|
||||
connection.connect();
|
||||
connection.disconnect();
|
||||
|
||||
result.put("connected", true);
|
||||
result.put("status", "online");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
result.put("connected", false);
|
||||
result.put("status", "offline");
|
||||
|
||||
}
|
||||
|
||||
result.put("type", "Bluetooth");
|
||||
result.put("address", address);
|
||||
String name = "";
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
|
||||
ContextCompat.checkSelfPermission(
|
||||
getContext(),
|
||||
Manifest.permission.BLUETOOTH_CONNECT
|
||||
) == PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
name = device.getName();
|
||||
}
|
||||
|
||||
result.put("name", name);
|
||||
|
||||
call.resolve(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Wi-Fi
|
||||
// --------------------------------------------------
|
||||
if ("Wi-Fi".equalsIgnoreCase(type)) {
|
||||
|
||||
String[] split = address.split(":");
|
||||
|
||||
Socket socket = new Socket();
|
||||
|
||||
try {
|
||||
|
||||
socket.connect(
|
||||
new InetSocketAddress(split[0], Integer.parseInt(split[1])),
|
||||
1500);
|
||||
|
||||
socket.close();
|
||||
|
||||
result.put("connected", true);
|
||||
result.put("status", "online");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
result.put("connected", false);
|
||||
result.put("status", "offline");
|
||||
}
|
||||
|
||||
result.put("type", "Wi-Fi");
|
||||
result.put("address", address);
|
||||
|
||||
call.resolve(result);
|
||||
return;
|
||||
}
|
||||
|
||||
call.reject("Unsupported printer type.");
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
call.reject(e.getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
|
||||
super.load();
|
||||
|
||||
UsbPrinter.initialize(getContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,63 +1,380 @@
|
|||
package com.pozo.printer;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.dantsu.escposprinter.EscPosPrinter;
|
||||
import com.dantsu.escposprinter.connection.usb.UsbConnection;
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.pozo.printer.helper.ReceiptBuilder;
|
||||
import com.pozo.printer.model.TokenItem;
|
||||
import com.pozo.printer.utils.EscPosUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class UsbPrinter {
|
||||
|
||||
public static List<JSObject> scanDevices(Context context) {
|
||||
List<JSObject> result = new ArrayList<>();
|
||||
UsbManager manager =
|
||||
(UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
if (manager == null) return result;
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (UsbDevice device : devices.values()) {
|
||||
private static final String ACTION_USB_PERMISSION =
|
||||
"com.pozo.printer.USB_PERMISSION";
|
||||
|
||||
public static UsbDevice connectedUsbDevice;
|
||||
|
||||
private static ConnectCallback pendingConnectCallback;
|
||||
|
||||
private static final BroadcastReceiver usbReceiver =
|
||||
new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
if (!ACTION_USB_PERMISSION.equals(intent.getAction())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingConnectCallback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean granted =
|
||||
intent.getBooleanExtra(
|
||||
UsbManager.EXTRA_PERMISSION_GRANTED,
|
||||
false);
|
||||
|
||||
UsbDevice device;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
||||
device = intent.getParcelableExtra(
|
||||
UsbManager.EXTRA_DEVICE,
|
||||
UsbDevice.class);
|
||||
|
||||
} else {
|
||||
|
||||
device = intent.getParcelableExtra(
|
||||
UsbManager.EXTRA_DEVICE);
|
||||
}
|
||||
|
||||
if (granted && device != null) {
|
||||
|
||||
connectedUsbDevice = device;
|
||||
|
||||
JSObject obj = new JSObject();
|
||||
|
||||
obj.put(
|
||||
"name",
|
||||
device.getProductName() != null ? device.getProductName() : "USB Device"
|
||||
);
|
||||
obj.put("address", String.valueOf(device.getDeviceId()));
|
||||
result.add(obj);
|
||||
}
|
||||
return result;
|
||||
device.getProductName() != null
|
||||
? device.getProductName()
|
||||
: "USB Printer");
|
||||
|
||||
obj.put(
|
||||
"address",
|
||||
String.valueOf(device.getDeviceId()));
|
||||
|
||||
obj.put("connected", true);
|
||||
|
||||
pendingConnectCallback.onConnected(obj);
|
||||
|
||||
} else {
|
||||
|
||||
pendingConnectCallback.onError(
|
||||
"USB Permission Denied");
|
||||
}
|
||||
|
||||
public static void print(
|
||||
Context context,
|
||||
byte[] data,
|
||||
String deviceIdStr
|
||||
) throws Exception {
|
||||
pendingConnectCallback = null;
|
||||
}
|
||||
};
|
||||
|
||||
public static void initialize(Context context) {
|
||||
|
||||
IntentFilter filter =
|
||||
new IntentFilter(ACTION_USB_PERMISSION);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
||||
context.registerReceiver(
|
||||
usbReceiver,
|
||||
filter,
|
||||
Context.RECEIVER_NOT_EXPORTED);
|
||||
|
||||
} else {
|
||||
|
||||
context.registerReceiver(
|
||||
usbReceiver,
|
||||
filter);
|
||||
}
|
||||
}
|
||||
|
||||
public static UsbDevice findUsbPrinter(Context context) {
|
||||
|
||||
UsbManager manager =
|
||||
(UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
if (manager == null) throw new Exception("USB service not available");
|
||||
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
UsbDevice targetDevice = null;
|
||||
if (manager == null)
|
||||
return null;
|
||||
|
||||
if (deviceIdStr != null) {
|
||||
int deviceId = Integer.parseInt(deviceIdStr);
|
||||
for (UsbDevice device : devices.values()) {
|
||||
if (device.getDeviceId() == deviceId) {
|
||||
targetDevice = device;
|
||||
for (UsbDevice device : manager.getDeviceList().values()) {
|
||||
|
||||
for (int i = 0; i < device.getInterfaceCount(); i++) {
|
||||
|
||||
if (device.getInterface(i).getInterfaceClass()
|
||||
== UsbConstants.USB_CLASS_PRINTER) {
|
||||
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Callback interface ─────────────────────────────────────────
|
||||
|
||||
public interface ConnectCallback {
|
||||
|
||||
void onConnected(JSObject printer);
|
||||
|
||||
void onError(String message);
|
||||
}
|
||||
|
||||
public interface PrintCallback {
|
||||
void onSuccess();
|
||||
|
||||
void onError(String message);
|
||||
}
|
||||
|
||||
// ─── Scan ───────────────────────────────────────────────────────
|
||||
|
||||
public static List<JSObject> scanDevices(Context context) {
|
||||
List<JSObject> result = new ArrayList<>();
|
||||
|
||||
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
if (manager == null) return result;
|
||||
|
||||
for (UsbDevice device : manager.getDeviceList().values()) {
|
||||
|
||||
boolean isPrinter = false;
|
||||
|
||||
for (int i = 0; i < device.getInterfaceCount(); i++) {
|
||||
UsbInterface usbInterface = device.getInterface(i);
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER) {
|
||||
isPrinter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrinter) {
|
||||
JSObject obj = new JSObject();
|
||||
obj.put("name", device.getProductName() != null ?
|
||||
device.getProductName() : "USB Printer");
|
||||
obj.put("address", String.valueOf(device.getDeviceId()));
|
||||
|
||||
result.add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final String TAG = "USB_CONNECT";
|
||||
|
||||
public static void connectUsb(
|
||||
Context context,
|
||||
ConnectCallback callback) throws Exception {
|
||||
|
||||
Log.d(TAG, "===== connectUsb() called =====");
|
||||
|
||||
UsbManager manager =
|
||||
(UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
|
||||
Log.d(TAG, "UsbManager: " + manager);
|
||||
|
||||
if (manager == null) {
|
||||
Log.e(TAG, "UsbManager unavailable");
|
||||
throw new Exception("UsbManager unavailable");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Searching for USB printer...");
|
||||
|
||||
UsbDevice printer = findUsbPrinter(context);
|
||||
|
||||
Log.d(TAG, "Printer found: " + printer);
|
||||
|
||||
if (printer == null) {
|
||||
Log.e(TAG, "No USB printer found");
|
||||
throw new Exception("No USB printer found");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Device Name: " + printer.getDeviceName());
|
||||
Log.d(TAG, "Product Name: " + printer.getProductName());
|
||||
Log.d(TAG, "Vendor ID: " + printer.getVendorId());
|
||||
Log.d(TAG, "Product ID: " + printer.getProductId());
|
||||
Log.d(TAG, "Device ID: " + printer.getDeviceId());
|
||||
|
||||
boolean hasPermission = manager.hasPermission(printer);
|
||||
Log.d(TAG, "Has USB Permission: " + hasPermission);
|
||||
|
||||
// Already has permission
|
||||
if (hasPermission) {
|
||||
|
||||
Log.d(TAG, "Permission already granted.");
|
||||
|
||||
connectedUsbDevice = printer;
|
||||
|
||||
JSObject obj = new JSObject();
|
||||
|
||||
obj.put(
|
||||
"name",
|
||||
printer.getProductName() != null
|
||||
? printer.getProductName()
|
||||
: "USB Printer");
|
||||
|
||||
obj.put(
|
||||
"address",
|
||||
String.valueOf(printer.getDeviceId()));
|
||||
|
||||
obj.put("connected", true);
|
||||
|
||||
Log.d(TAG, "Calling callback.onConnected()");
|
||||
callback.onConnected(obj);
|
||||
|
||||
Log.d(TAG, "connectUsb() completed successfully.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Permission NOT granted. Requesting permission...");
|
||||
|
||||
// Save callback until permission result arrives
|
||||
pendingConnectCallback = callback;
|
||||
|
||||
int flags;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
|
||||
flags =
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
| PendingIntent.FLAG_MUTABLE;
|
||||
|
||||
Log.d(TAG, "Using FLAG_MUTABLE");
|
||||
|
||||
} else {
|
||||
if (!devices.isEmpty()) {
|
||||
targetDevice = devices.values().iterator().next();
|
||||
|
||||
flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
|
||||
Log.d(TAG, "Using FLAG_UPDATE_CURRENT");
|
||||
}
|
||||
|
||||
PendingIntent permissionIntent =
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
new Intent(ACTION_USB_PERMISSION),
|
||||
flags);
|
||||
|
||||
Log.d(TAG, "PendingIntent created.");
|
||||
|
||||
manager.requestPermission(
|
||||
printer,
|
||||
permissionIntent);
|
||||
|
||||
Log.d(TAG, "requestPermission() called. Waiting for BroadcastReceiver...");
|
||||
}
|
||||
|
||||
// ─── Find device ────────────────────────────────────────────────
|
||||
|
||||
public static UsbDevice findDevice(UsbManager manager, String deviceIdStr) {
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
if (deviceIdStr != null) {
|
||||
int id = Integer.parseInt(deviceIdStr);
|
||||
for (UsbDevice d : devices.values()) {
|
||||
if (d.getDeviceId() == id) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return devices.isEmpty() ? null : devices.values().iterator().next();
|
||||
}
|
||||
|
||||
// ─── Print with auto permission ─────────────────────────────────
|
||||
|
||||
public static void printWithPermission(
|
||||
Context context,
|
||||
byte[] data,
|
||||
String deviceIdStr,
|
||||
PrintCallback callback
|
||||
) {
|
||||
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
if (manager == null) {
|
||||
callback.onError("USB service not available");
|
||||
return;
|
||||
}
|
||||
|
||||
UsbDevice target = findDevice(manager, deviceIdStr);
|
||||
if (target == null) {
|
||||
callback.onError("USB printer not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (manager.hasPermission(target)) {
|
||||
// ── Already permitted → print immediately ──
|
||||
new Thread(() -> {
|
||||
try {
|
||||
sendData(manager, target, data);
|
||||
callback.onSuccess();
|
||||
} catch (Exception e) {
|
||||
callback.onError(e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
// ── No permission → show dialog, then print on grant ──
|
||||
PendingIntent pi = PendingIntent.getBroadcast(
|
||||
context, 0,
|
||||
new Intent(ACTION_USB_PERMISSION),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
if (!ACTION_USB_PERMISSION.equals(intent.getAction())) return;
|
||||
context.unregisterReceiver(this);
|
||||
|
||||
boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
|
||||
if (granted) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
sendData(manager, target, data);
|
||||
callback.onSuccess();
|
||||
} catch (Exception e) {
|
||||
callback.onError(e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
callback.onError("USB permission denied by user");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
context.registerReceiver(receiver, new IntentFilter(ACTION_USB_PERMISSION));
|
||||
manager.requestPermission(target, pi);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetDevice == null) throw new Exception("USB printer not found");
|
||||
if (!manager.hasPermission(targetDevice))
|
||||
throw new Exception("USB permission denied — prompt user first");
|
||||
// ─── Raw data transfer ──────────────────────────────────────────
|
||||
|
||||
UsbInterface intf = targetDevice.getInterface(0);
|
||||
private static void sendData(UsbManager manager, UsbDevice device, byte[] data) throws Exception {
|
||||
Log.d("Gokullog", "sendData: " + device.getProductName());
|
||||
|
||||
UsbInterface intf = device.getInterface(0);
|
||||
UsbEndpoint endpoint = null;
|
||||
for (int i = 0; i < intf.getEndpointCount(); i++) {
|
||||
UsbEndpoint ep = intf.getEndpoint(i);
|
||||
|
|
@ -68,7 +385,7 @@ public class UsbPrinter {
|
|||
}
|
||||
if (endpoint == null) throw new Exception("No OUT endpoint on USB device");
|
||||
|
||||
UsbDeviceConnection connection = manager.openDevice(targetDevice);
|
||||
UsbDeviceConnection connection = manager.openDevice(device);
|
||||
if (connection == null) throw new Exception("Cannot open USB device");
|
||||
|
||||
connection.claimInterface(intf, true);
|
||||
|
|
@ -79,7 +396,8 @@ public class UsbPrinter {
|
|||
int size = Math.min(chunkSize, data.length - offset);
|
||||
byte[] chunk = new byte[size];
|
||||
System.arraycopy(data, offset, chunk, 0, size);
|
||||
connection.bulkTransfer(endpoint, chunk, size, 5000);
|
||||
int transferred = connection.bulkTransfer(endpoint, chunk, size, 5000);
|
||||
if (transferred < 0) throw new Exception("Transfer failed at offset " + offset);
|
||||
offset += size;
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -87,4 +405,258 @@ public class UsbPrinter {
|
|||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void printUsb(
|
||||
Context context,
|
||||
String usbDeviceId,
|
||||
String receiptText,
|
||||
String estimateText,
|
||||
String logoImage,
|
||||
String totalAmount,
|
||||
String estimateAmount,
|
||||
String footerPrint,
|
||||
String upiId,
|
||||
List<List<TokenItem>> salesTokens,
|
||||
List<List<TokenItem>> estimateTokens,
|
||||
String orderId,
|
||||
String estimateOrderId,
|
||||
String salesId,
|
||||
String salesIdEst) throws Exception {
|
||||
|
||||
UsbManager usbManager =
|
||||
(UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
|
||||
HashMap<String, UsbDevice> deviceList =
|
||||
usbManager.getDeviceList();
|
||||
|
||||
UsbDevice selectedDevice = null;
|
||||
|
||||
for (UsbDevice device : deviceList.values()) {
|
||||
|
||||
if (String.valueOf(device.getDeviceId()).equals(usbDeviceId)) {
|
||||
|
||||
selectedDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedDevice == null) {
|
||||
throw new Exception("USB printer not found");
|
||||
}
|
||||
|
||||
if (!usbManager.hasPermission(selectedDevice)) {
|
||||
|
||||
PendingIntent permissionIntent =
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
new Intent(ACTION_USB_PERMISSION),
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
BroadcastReceiver usbReceiver =
|
||||
new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(
|
||||
Context ctx,
|
||||
Intent intent) {
|
||||
|
||||
if (!ACTION_USB_PERMISSION.equals(
|
||||
intent.getAction())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ctx.unregisterReceiver(this);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
UsbDevice device;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
||||
device = intent.getParcelableExtra(
|
||||
UsbManager.EXTRA_DEVICE,
|
||||
UsbDevice.class);
|
||||
|
||||
} else {
|
||||
|
||||
device = intent.getParcelableExtra(
|
||||
UsbManager.EXTRA_DEVICE);
|
||||
}
|
||||
|
||||
boolean granted =
|
||||
intent.getBooleanExtra(
|
||||
UsbManager.EXTRA_PERMISSION_GRANTED,
|
||||
false);
|
||||
|
||||
if (!granted || device == null) {
|
||||
|
||||
Log.e("USB", "Permission denied");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
printUsbInternal(
|
||||
usbManager,
|
||||
device,
|
||||
receiptText,
|
||||
estimateText,
|
||||
logoImage,
|
||||
totalAmount,
|
||||
estimateAmount,
|
||||
footerPrint,
|
||||
upiId,
|
||||
salesTokens,
|
||||
estimateTokens,
|
||||
orderId,
|
||||
estimateOrderId,
|
||||
salesId,
|
||||
salesIdEst);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.e("USB", "Print failed", e);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
usbReceiver,
|
||||
new IntentFilter(ACTION_USB_PERMISSION),
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED);
|
||||
|
||||
usbManager.requestPermission(
|
||||
selectedDevice,
|
||||
permissionIntent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
printUsbInternal(
|
||||
usbManager,
|
||||
selectedDevice,
|
||||
receiptText,
|
||||
estimateText,
|
||||
logoImage,
|
||||
totalAmount,
|
||||
estimateAmount,
|
||||
footerPrint,
|
||||
upiId,
|
||||
salesTokens,
|
||||
estimateTokens,
|
||||
orderId,
|
||||
estimateOrderId,
|
||||
salesId,
|
||||
salesIdEst);
|
||||
}
|
||||
|
||||
private static void printUsbInternal(
|
||||
UsbManager usbManager,
|
||||
UsbDevice selectedDevice,
|
||||
String receiptText,
|
||||
String estimateText,
|
||||
String logoImage,
|
||||
String totalAmount,
|
||||
String estimateAmount,
|
||||
String footerPrint,
|
||||
String upiId,
|
||||
List<List<TokenItem>> salesTokens,
|
||||
List<List<TokenItem>> estimateTokens,
|
||||
String orderId,
|
||||
String estimateOrderId,
|
||||
String salesId,
|
||||
String salesIdEst) throws Exception {
|
||||
|
||||
UsbConnection connection =
|
||||
new UsbConnection(
|
||||
usbManager,
|
||||
selectedDevice);
|
||||
|
||||
long t = System.currentTimeMillis();
|
||||
|
||||
EscPosPrinter printer =
|
||||
new EscPosPrinter(
|
||||
connection,
|
||||
203,
|
||||
80f,
|
||||
45);
|
||||
|
||||
Log.d("USB_TIME",
|
||||
"Printer = "
|
||||
+ (System.currentTimeMillis() - t));
|
||||
|
||||
if (receiptText != null &&
|
||||
!receiptText.isEmpty()) {
|
||||
|
||||
t = System.currentTimeMillis();
|
||||
|
||||
String receipt =
|
||||
ReceiptBuilder.buildReceipt(
|
||||
printer,
|
||||
receiptText,
|
||||
logoImage,
|
||||
upiId,
|
||||
totalAmount,
|
||||
footerPrint);
|
||||
|
||||
Log.d("USB_TIME",
|
||||
"Receipt = "
|
||||
+ (System.currentTimeMillis() - t));
|
||||
|
||||
t = System.currentTimeMillis();
|
||||
|
||||
printer.printFormattedTextAndCut(receipt);
|
||||
|
||||
Log.d("USB_TIME",
|
||||
"Print = "
|
||||
+ (System.currentTimeMillis() - t));
|
||||
}
|
||||
|
||||
if (estimateText != null &&
|
||||
!estimateText.isEmpty()) {
|
||||
|
||||
String estimate =
|
||||
ReceiptBuilder.buildReceipt(
|
||||
printer,
|
||||
estimateText,
|
||||
logoImage,
|
||||
upiId,
|
||||
estimateAmount,
|
||||
footerPrint);
|
||||
|
||||
printer.printFormattedTextAndCut(estimate);
|
||||
}
|
||||
|
||||
if (salesTokens != null &&
|
||||
!salesTokens.isEmpty()) {
|
||||
|
||||
EscPosUtils.printUsbOrNetworkTokens(
|
||||
printer,
|
||||
salesTokens,
|
||||
orderId,
|
||||
salesId,
|
||||
"Sales");
|
||||
}
|
||||
|
||||
if (estimateTokens != null &&
|
||||
!estimateTokens.isEmpty()) {
|
||||
|
||||
EscPosUtils.printUsbOrNetworkTokens(
|
||||
printer,
|
||||
estimateTokens,
|
||||
estimateOrderId,
|
||||
salesIdEst,
|
||||
"Estimate");
|
||||
}
|
||||
|
||||
printer.disconnectPrinter();
|
||||
|
||||
Log.d("USB", "Print Success");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.pozo.printer.helper;
|
||||
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dantsu.escposprinter.EscPosPrinter;
|
||||
import com.dantsu.escposprinter.textparser.PrinterTextParserImg;
|
||||
import com.pozo.printer.utils.EscPosUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class LogoCache {
|
||||
|
||||
private static byte[] cachedBluetoothLogo;
|
||||
|
||||
// USB (DantSu) hex string
|
||||
private static String cachedUsbLogoHex;
|
||||
|
||||
private static String cachedLogoUrl;
|
||||
|
||||
public static Bitmap cachedBitmap;
|
||||
|
||||
public static Bitmap getCachedBitmap(String url) {
|
||||
|
||||
try {
|
||||
|
||||
if (cachedBitmap != null
|
||||
&& !cachedBitmap.isRecycled()
|
||||
&& url.equals(cachedLogoUrl)) {
|
||||
|
||||
Log.d("LOGO_CACHE", "Using cached bitmap");
|
||||
return cachedBitmap;
|
||||
}
|
||||
|
||||
Log.d("LOGO_CACHE", "Downloading logo");
|
||||
|
||||
URL logoUrl = new URL(url);
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) logoUrl.openConnection();
|
||||
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
|
||||
InputStream input = connection.getInputStream();
|
||||
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(input);
|
||||
|
||||
input.close();
|
||||
connection.disconnect();
|
||||
|
||||
cachedBitmap = bitmap;
|
||||
cachedLogoUrl = url;
|
||||
|
||||
// invalidate converted caches
|
||||
cachedBluetoothLogo = null;
|
||||
cachedUsbLogoHex = null;
|
||||
|
||||
return cachedBitmap;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.e("Bitmap", "Download failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getBluetoothLogo(String logoUrl) throws Exception {
|
||||
|
||||
if (cachedBluetoothLogo != null &&
|
||||
logoUrl.equals(cachedLogoUrl)) {
|
||||
|
||||
Log.d("LOGO_CACHE", "Bluetooth logo cache");
|
||||
return cachedBluetoothLogo;
|
||||
}
|
||||
|
||||
Bitmap bitmap = getCachedBitmap(logoUrl);
|
||||
|
||||
if (bitmap == null)
|
||||
return null;
|
||||
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(
|
||||
bitmap,
|
||||
220,
|
||||
220,
|
||||
true);
|
||||
|
||||
Bitmap rgb565 = scaled.copy(
|
||||
Bitmap.Config.RGB_565,
|
||||
false);
|
||||
|
||||
Bitmap centered = EscPosUtils.centerBitmap(
|
||||
rgb565,
|
||||
576);
|
||||
|
||||
cachedBluetoothLogo = EscPosUtils.convertBitmapToEscPos(centered);
|
||||
|
||||
// recycle temporary bitmaps only
|
||||
if (scaled != bitmap)
|
||||
scaled.recycle();
|
||||
|
||||
if (rgb565 != scaled)
|
||||
rgb565.recycle();
|
||||
|
||||
if (centered != rgb565)
|
||||
centered.recycle();
|
||||
|
||||
return cachedBluetoothLogo;
|
||||
}
|
||||
|
||||
public static String getUsbLogo(
|
||||
EscPosPrinter printer,
|
||||
String logoUrl) throws Exception {
|
||||
|
||||
if (cachedUsbLogoHex != null &&
|
||||
logoUrl.equals(cachedLogoUrl)) {
|
||||
|
||||
Log.d("LOGO_CACHE", "USB logo cache");
|
||||
return cachedUsbLogoHex;
|
||||
}
|
||||
|
||||
Bitmap bitmap = getCachedBitmap(logoUrl);
|
||||
|
||||
if (bitmap == null)
|
||||
return "";
|
||||
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(
|
||||
bitmap,
|
||||
170,
|
||||
170,
|
||||
true);
|
||||
|
||||
// Center on 576-dot canvas (80mm printer)
|
||||
Bitmap centered = EscPosUtils.centerBitmap(scaled, 576);
|
||||
|
||||
cachedUsbLogoHex = PrinterTextParserImg.bitmapToHexadecimalString(
|
||||
printer,
|
||||
centered);
|
||||
|
||||
if (centered != scaled) {
|
||||
centered.recycle();
|
||||
}
|
||||
|
||||
if (scaled != bitmap) {
|
||||
scaled.recycle();
|
||||
}
|
||||
|
||||
return cachedUsbLogoHex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
package com.pozo.printer.helper;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dantsu.escposprinter.EscPosPrinter;
|
||||
import com.dantsu.escposprinter.textparser.PrinterTextParserImg;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
public class ReceiptBuilder {
|
||||
|
||||
public static String buildReceipt(
|
||||
EscPosPrinter printer,
|
||||
String receiptText,
|
||||
String logoImage,
|
||||
String upiId,
|
||||
String amount,
|
||||
String footerPrint) {
|
||||
|
||||
String imageDisplay = "";
|
||||
String qrCodeSection = "";
|
||||
String footerVisible = "";
|
||||
|
||||
// -----------------------------
|
||||
// LOGO
|
||||
// -----------------------------
|
||||
if (logoImage != null && !logoImage.trim().isEmpty()) {
|
||||
|
||||
try {
|
||||
|
||||
long t = System.currentTimeMillis();
|
||||
|
||||
String logoHex = LogoCache.getUsbLogo(printer, logoImage);
|
||||
|
||||
Log.d("USB_TIME", "Logo = " + (System.currentTimeMillis() - t));
|
||||
|
||||
if (logoHex != null && !logoHex.isEmpty()) {
|
||||
|
||||
imageDisplay = "[C]<img>"
|
||||
+ logoHex
|
||||
+ "</img>\n";
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.e("Printer", "Logo Error", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// UPI QR
|
||||
// -----------------------------
|
||||
Log.d("GokulLog", "Upi" + upiId);
|
||||
Log.d("GokulLog", "amount" + amount);
|
||||
if (upiId != null && !upiId.trim().isEmpty()
|
||||
&& amount != null
|
||||
&& !amount.trim().isEmpty()) {
|
||||
|
||||
try {
|
||||
|
||||
String upiString = "upi://pay?pa=" +
|
||||
upiId +
|
||||
"&pn=Merchant" +
|
||||
"&am=" +
|
||||
amount +
|
||||
"&cu=INR";
|
||||
|
||||
Bitmap qrBitmap = generateQRCode(upiString);
|
||||
|
||||
qrCodeSection = "[C]<img>"
|
||||
+ PrinterTextParserImg.bitmapToHexadecimalString(
|
||||
printer,
|
||||
qrBitmap)
|
||||
+ "</img>\n"
|
||||
+ "[C]<b><font size='small'>Scan To Pay:</font></b>\n"
|
||||
+ "[C]Rs :- " + amount + "\n";
|
||||
|
||||
qrBitmap.recycle();
|
||||
|
||||
} catch (WriterException e) {
|
||||
|
||||
Log.e("Receipt", "QR Generation Error", e);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.e("Receipt", "Unexpected QR Error", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// FOOTER
|
||||
// -----------------------------
|
||||
if ("Yes".equalsIgnoreCase(footerPrint)) {
|
||||
|
||||
footerVisible = "[C]<b>Thank You! Visit Again</b>\n";
|
||||
|
||||
} else {
|
||||
|
||||
footerVisible = "";
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// FINAL RECEIPT
|
||||
// -----------------------------
|
||||
return imageDisplay
|
||||
+ receiptText
|
||||
+ qrCodeSection
|
||||
+ footerVisible
|
||||
+ "[L]\n\n\n";
|
||||
}
|
||||
|
||||
private static Bitmap generateQRCode(String text) throws WriterException {
|
||||
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(
|
||||
text,
|
||||
BarcodeFormat.QR_CODE,
|
||||
450,
|
||||
450);
|
||||
|
||||
int width = bitMatrix.getWidth();
|
||||
int height = bitMatrix.getHeight();
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
bitmap.setPixel(
|
||||
x,
|
||||
y,
|
||||
bitMatrix.get(x, y)
|
||||
? Color.BLACK
|
||||
: Color.WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static String trimText(String text, int maxLength) {
|
||||
|
||||
if (text == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (text.length() <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.substring(0, maxLength);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pozo.printer.model;
|
||||
|
||||
public class TokenItem {
|
||||
public int slNo;
|
||||
public String foodName;
|
||||
public double qty;
|
||||
|
||||
public double rate;
|
||||
public double price;
|
||||
|
||||
public int getSlNo() {
|
||||
return slNo;
|
||||
}
|
||||
|
||||
public void setSlNo(int slNo) {
|
||||
this.slNo = slNo;
|
||||
}
|
||||
|
||||
public String getFoodName() {
|
||||
return foodName;
|
||||
}
|
||||
|
||||
public void setFoodName(String foodName) {
|
||||
this.foodName = foodName;
|
||||
}
|
||||
|
||||
public double getQty() {
|
||||
return qty;
|
||||
}
|
||||
|
||||
public void setQty(double qty) {
|
||||
this.qty = qty;
|
||||
}
|
||||
|
||||
public double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public void setRate(double rate) {
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
public double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
package com.pozo.printer.utils;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dantsu.escposprinter.EscPosPrinter;
|
||||
import com.pozo.printer.helper.ReceiptBuilder;
|
||||
import com.pozo.printer.model.TokenItem;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class EscPosUtils {
|
||||
|
||||
public static Bitmap centerBitmap(Bitmap bitmap, int printerWidth) {
|
||||
|
||||
Bitmap output = Bitmap.createBitmap(
|
||||
printerWidth,
|
||||
bitmap.getHeight(),
|
||||
Bitmap.Config.RGB_565);
|
||||
|
||||
Canvas canvas = new Canvas(output);
|
||||
|
||||
// White background
|
||||
canvas.drawColor(Color.WHITE);
|
||||
|
||||
int left = (printerWidth - bitmap.getWidth()) / 2;
|
||||
|
||||
canvas.drawBitmap(bitmap, left, 0, null);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static byte[] generateFooter(String footerPrint) {
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
if ("Yes".equalsIgnoreCase(footerPrint)) {
|
||||
buffer.write(new byte[] { 0x1B, 0x61, 0x01 });
|
||||
buffer.write("\n".getBytes(StandardCharsets.UTF_8));
|
||||
buffer.write("Thank You! Visit Again\n\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
buffer.write("\n\n".getBytes(StandardCharsets.UTF_8));
|
||||
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("Footer", "Footer Error", e);
|
||||
}
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] convertBitmapToEscPos(Bitmap bitmap) {
|
||||
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
int bytesPerLine = (width + 7) / 8;
|
||||
byte[] imageData = new byte[bytesPerLine * height];
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
|
||||
for (int x = 0; x < bytesPerLine; x++) {
|
||||
|
||||
byte slice = 0;
|
||||
|
||||
for (int b = 0; b < 8; b++) {
|
||||
|
||||
int pixelX = x * 8 + b;
|
||||
|
||||
if (pixelX < width) {
|
||||
|
||||
int pixel = bitmap.getPixel(pixelX, y);
|
||||
|
||||
int r = Color.red(pixel);
|
||||
int g = Color.green(pixel);
|
||||
int bl = Color.blue(pixel);
|
||||
|
||||
int gray = (r + g + bl) / 3;
|
||||
|
||||
if (gray < 128) {
|
||||
slice |= (byte) (1 << (7 - b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageData[index++] = slice;
|
||||
}
|
||||
}
|
||||
|
||||
// Center align
|
||||
stream.write(new byte[] { 0x1B, 0x61, 0x01 });
|
||||
|
||||
// GS v 0 command
|
||||
stream.write(new byte[] {
|
||||
0x1D,
|
||||
0x76,
|
||||
0x30,
|
||||
0x00,
|
||||
(byte) (bytesPerLine % 256),
|
||||
(byte) (bytesPerLine / 256),
|
||||
(byte) (height % 256),
|
||||
(byte) (height / 256)
|
||||
});
|
||||
|
||||
Log.d("IMAGE_DEBUG", "ImageData Size : " + imageData.length);
|
||||
stream.write(imageData);
|
||||
|
||||
stream.write(new byte[] { 0x0A, 0x0A });
|
||||
|
||||
// Left align
|
||||
stream.write(new byte[] { 0x1B, 0x61, 0x00 });
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("BitmapError", "Error converting bitmap", e);
|
||||
}
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] generateQRCodeEscPos(String data) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
int dataLength = dataBytes.length;
|
||||
|
||||
// QR Code model
|
||||
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x04, 0x00, 0x31, 0x41, 0x32, 0x00 }); // Model 2
|
||||
|
||||
// QR Code size (1-8, 5 is good for mobile)
|
||||
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, 0x05 }); // Size 5
|
||||
|
||||
// QR Code error correction level (L=48, M=49, Q=50, H=51)
|
||||
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, 0x31 }); // Level M
|
||||
|
||||
// Store QR code data
|
||||
int pL = (dataLength + 3) % 256;
|
||||
int pH = (dataLength + 3) / 256;
|
||||
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, (byte) pL, (byte) pH, 0x31, 0x50, 0x30 });
|
||||
buffer.write(dataBytes);
|
||||
|
||||
// Print QR code
|
||||
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30 });
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("QRCodeError", "Error generating QR code", e);
|
||||
}
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public static void printUsbOrNetworkTokens(EscPosPrinter printer,
|
||||
List<List<TokenItem>> salesTokens,
|
||||
String orderId,
|
||||
String salesId,
|
||||
String salesOrEstimate) throws Exception {
|
||||
Log.d("TokenPrint", "salesTokens" + salesTokens);
|
||||
Log.d("TokenPrint", "orderId" + orderId);
|
||||
if (salesTokens == null || salesTokens.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (List<TokenItem> token : salesTokens) {
|
||||
|
||||
String tokenText = EscPosUtils.buildTokenDesign(token, orderId, salesId, salesOrEstimate);
|
||||
|
||||
printer.printFormattedTextAndCut(tokenText);
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildTokenDesign(
|
||||
List<TokenItem> token,
|
||||
String orderId,
|
||||
String salesId,
|
||||
String salesOrEstimate) {
|
||||
|
||||
String currentDate = new SimpleDateFormat(
|
||||
"dd-MM-yyyy",
|
||||
Locale.ENGLISH).format(new Date());
|
||||
|
||||
String currentTime = new SimpleDateFormat(
|
||||
"hh:mm a",
|
||||
Locale.ENGLISH).format(new Date());
|
||||
|
||||
StringBuilder bookingData = new StringBuilder();
|
||||
|
||||
for (TokenItem item : token) {
|
||||
|
||||
bookingData.append(String.format(Locale.ENGLISH,
|
||||
"%2d %-20s %4.0f %6.0f %7.0f\n",
|
||||
item.slNo,
|
||||
ReceiptBuilder.trimText(item.foodName, 20),
|
||||
item.qty,
|
||||
item.rate,
|
||||
item.price));
|
||||
}
|
||||
|
||||
String tokenHeading = "";
|
||||
|
||||
if ("Estimate".equalsIgnoreCase(salesOrEstimate)) {
|
||||
tokenHeading = " (" + salesOrEstimate + ")";
|
||||
}
|
||||
|
||||
return "[C]<b><font size='big'><u>Token" + tokenHeading + "</u></font></b>\n" +
|
||||
"[C]\n" +
|
||||
"[L]<b>Bill No : " + orderId + "</b>" +
|
||||
"[R]<b>" + currentDate + ", " + currentTime + "</b>\n" +
|
||||
"[C]------------------------------------------------\n" +
|
||||
"[L]<b>S.No Item Qty Rate Price</b>\n" +
|
||||
"[C]------------------------------------------------\n" +
|
||||
bookingData +
|
||||
"[C]<b>------------------------------------------------</b>\n" +
|
||||
"[C]\n" +
|
||||
"[C]<font size='normal'>YOUR ORDER NO : " + salesId + "</font>\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -117,50 +117,6 @@
|
|||
"@capacitor/core": "^8.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/cst-dts-gen": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
|
||||
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chevrotain/gast": "11.0.3",
|
||||
"@chevrotain/types": "11.0.3",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/gast": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
|
||||
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chevrotain/types": "11.0.3",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/regexp-to-ast": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
|
||||
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@chevrotain/types": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
|
||||
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@chevrotain/utils": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
|
||||
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
||||
|
|
@ -1326,9 +1282,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -1426,35 +1382,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chevrotain": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
|
||||
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@chevrotain/cst-dts-gen": "11.0.3",
|
||||
"@chevrotain/gast": "11.0.3",
|
||||
"@chevrotain/regexp-to-ast": "11.0.3",
|
||||
"@chevrotain/types": "11.0.3",
|
||||
"@chevrotain/utils": "11.0.3",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/chevrotain-allstar": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
|
||||
"integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"chevrotain": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -2361,9 +2288,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz",
|
||||
"integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
|
@ -2578,9 +2505,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -3285,18 +3212,6 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/java-parser": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/java-parser/-/java-parser-3.0.1.tgz",
|
||||
"integrity": "sha512-sDIR7u9b7O2JViNUxiZRhnRz7URII/eE7g2B+BmGxDeS6Ex3OYAcCyz5oh0H4LQ+hL/BS8OJTz8apMy9xtGmrQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"chevrotain": "11.0.3",
|
||||
"chevrotain-allstar": "0.3.1",
|
||||
"lodash": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
|
@ -3305,10 +3220,20 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
|
||||
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
|
|
@ -3418,20 +3343,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
|
|
@ -3823,9 +3734,9 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -3873,13 +3784,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-java": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-2.8.1.tgz",
|
||||
"integrity": "sha512-tkteH5OSCEb0E7wKnhhUSitr1pGUCUt9M//CwerSNhoalL/qv0jXTeSVBPZ36KC+kZl3nbq4dxh144NuGchACg==",
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-2.10.0.tgz",
|
||||
"integrity": "sha512-lDmVgJU/TCBri3kITMcNyr712Ac5Totb1fOp9GW1KCUdv4SsPfbFP7I+mUdRFEoPzE72ymf8MOgIsd/v0yA9CA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"java-parser": "3.0.1"
|
||||
"web-tree-sitter": "0.26.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": "^3.0.0"
|
||||
|
|
@ -4756,6 +4667,13 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-tree-sitter": {
|
||||
"version": "0.26.9",
|
||||
"resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.9.tgz",
|
||||
"integrity": "sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,50 @@ export interface PermissionResult {
|
|||
network: 'granted' | 'denied' | 'prompt';
|
||||
}
|
||||
|
||||
export interface NetworkPrinter {
|
||||
name: string;
|
||||
address: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface PrintIntentOptions {
|
||||
receiptText: string;
|
||||
estimateText: string;
|
||||
logoImage: string;
|
||||
|
||||
tokenDataSales: any[];
|
||||
tokenDataEstimate: any[];
|
||||
|
||||
orderId: string;
|
||||
estimateOrderId: string;
|
||||
|
||||
footerPrint: string;
|
||||
upiId: string;
|
||||
|
||||
totalAmount: string;
|
||||
estimateAmount: string;
|
||||
}
|
||||
|
||||
export interface UsbConnectionResult {
|
||||
name: string;
|
||||
address: string;
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
export interface PrinterStatusOptions {
|
||||
type: 'USB' | 'Bluetooth' | 'Wi-Fi';
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface PrinterStatusResult {
|
||||
connected: boolean;
|
||||
status: 'online' | 'offline';
|
||||
type: string;
|
||||
address: string;
|
||||
name?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @capacitor-pozo-printer
|
||||
*/
|
||||
|
|
@ -39,6 +83,16 @@ export interface PrinterPlugin {
|
|||
*/
|
||||
scanUsb(): Promise<{ devices: PrinterDevice[] }>;
|
||||
|
||||
/**
|
||||
* Connect to USB printer
|
||||
*/
|
||||
connectUsb(): Promise<UsbConnectionResult>;
|
||||
|
||||
/**
|
||||
* Scan Wi-Fi / LAN printers
|
||||
*/
|
||||
scanNetwork(): Promise<{ devices: NetworkPrinter[] }>;
|
||||
|
||||
/**
|
||||
* Print via Bluetooth SPP
|
||||
*/
|
||||
|
|
@ -55,4 +109,12 @@ export interface PrinterPlugin {
|
|||
printUsb(options: UsbPrintOptions): Promise<{ success: boolean }>;
|
||||
|
||||
|
||||
printIntent(options: PrintIntentOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Check printer online/offline status
|
||||
*/
|
||||
checkPrinterStatus(
|
||||
options: PrinterStatusOptions
|
||||
): Promise<PrinterStatusResult>;
|
||||
}
|
||||
41
src/web.ts
41
src/web.ts
|
|
@ -2,10 +2,14 @@ import { WebPlugin } from '@capacitor/core';
|
|||
import type {
|
||||
PrinterPlugin,
|
||||
PrinterDevice,
|
||||
NetworkPrinter,
|
||||
UsbConnectionResult,
|
||||
BluetoothPrintOptions,
|
||||
NetworkPrintOptions,
|
||||
UsbPrintOptions,
|
||||
PermissionResult
|
||||
PermissionResult,
|
||||
PrinterStatusOptions,
|
||||
PrinterStatusResult
|
||||
} from './definitions';
|
||||
|
||||
export class PrinterWeb extends WebPlugin implements PrinterPlugin {
|
||||
|
|
@ -19,6 +23,21 @@ export class PrinterWeb extends WebPlugin implements PrinterPlugin {
|
|||
return { devices: [] };
|
||||
}
|
||||
|
||||
async connectUsb(): Promise<UsbConnectionResult> {
|
||||
console.warn('USB connection not available on web');
|
||||
|
||||
return {
|
||||
name: '',
|
||||
address: '',
|
||||
connected: false
|
||||
};
|
||||
}
|
||||
|
||||
async scanNetwork(): Promise<{ devices: NetworkPrinter[] }> {
|
||||
console.warn('Network scan not available on web');
|
||||
return { devices: [] };
|
||||
}
|
||||
|
||||
async printBluetooth(_options: BluetoothPrintOptions): Promise<{ success: boolean }> {
|
||||
console.warn('Bluetooth printing not available on web');
|
||||
return { success: false };
|
||||
|
|
@ -37,4 +56,24 @@ export class PrinterWeb extends WebPlugin implements PrinterPlugin {
|
|||
async requestPermissions(): Promise<PermissionResult> {
|
||||
return { bluetooth: 'granted', network: 'granted' };
|
||||
}
|
||||
|
||||
async printIntent(_options: any): Promise<void> {
|
||||
console.warn('printIntent not available on web');
|
||||
}
|
||||
|
||||
async checkPrinterStatus(
|
||||
_options: PrinterStatusOptions
|
||||
): Promise<PrinterStatusResult> {
|
||||
|
||||
console.warn("Printer status not available on web");
|
||||
|
||||
return {
|
||||
connected: false,
|
||||
status: "offline",
|
||||
type: _options.type,
|
||||
address: _options.address,
|
||||
message: "Not supported on web"
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue