feat: Implement logo printing to thermal printer and add loading indicator
• Replace StrukTextGenerator with EscPosPrintService to support image printing • Add loading indicator during print process to prevent UI freeze • Fix Uint8List type issues for printer compatibility • Improve error handling with proper state management • Users can now print selected store logo on thermal receiptsmaster
							parent
							
								
									b848881805
								
							
						
					
					
						commit
						3e1c34d1ce
					
				| 
						 | 
				
			
			@ -1,12 +1,14 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
// import 'dart:io'; // Add this import for file operations // PDF Removed
 | 
			
		||||
import 'dart:typed_data'; // Untuk Uint8List
 | 
			
		||||
import 'package:intl/intl.dart'; // Untuk format angka
 | 
			
		||||
import 'package:cashumit/models/receipt_item.dart';
 | 
			
		||||
import 'package:cashumit/screens/add_item_screen.dart';
 | 
			
		||||
// import 'package:cashumit/services/pdf_export_service.dart'; // PDF Removed
 | 
			
		||||
import 'package:cashumit/services/firefly_api_service.dart';
 | 
			
		||||
import 'package:cashumit/services/struk_text_generator.dart'; // Tambahkan import ini
 | 
			
		||||
import 'package:cashumit/services/esc_pos_print_service.dart'; // Tambahkan import ini
 | 
			
		||||
import 'package:bluetooth_print/bluetooth_print.dart';
 | 
			
		||||
import 'package:bluetooth_print/bluetooth_print_model.dart';
 | 
			
		||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart'; // Tambahkan import ini
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +60,7 @@ class _ReceiptScreenState extends State<ReceiptScreen> {
 | 
			
		|||
	late BluetoothPrint bluetoothPrint;
 | 
			
		||||
	bool _bluetoothConnected = false;
 | 
			
		||||
	BluetoothDevice? _bluetoothDevice;
 | 
			
		||||
	bool _isPrinting = false; // State variable to track printing status
 | 
			
		||||
 | 
			
		||||
	@override
 | 
			
		||||
		void initState() {
 | 
			
		||||
| 
						 | 
				
			
			@ -374,51 +377,31 @@ class _ReceiptScreenState extends State<ReceiptScreen> {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			print('Menghasilkan teks struk...');
 | 
			
		||||
			print('Menghasilkan byte array ESC/POS menggunakan flutter_esc_pos_utils...');
 | 
			
		||||
			print('Jumlah item: ${items.length}');
 | 
			
		||||
			print('Tanggal transaksi: $_transactionDate');
 | 
			
		||||
			print('ID kasir: $cashierId');
 | 
			
		||||
			print('ID transaksi: $transactionId');
 | 
			
		||||
			
 | 
			
		||||
			// Generate struk text menggunakan StrukTextGenerator
 | 
			
		||||
			final strukText = await StrukTextGenerator.generateStrukText(
 | 
			
		||||
			// Generate struk dalam format byte array menggunakan EscPosPrintService
 | 
			
		||||
			final bytes = await EscPosPrintService.generateEscPosBytes(
 | 
			
		||||
				items: items,
 | 
			
		||||
				transactionDate: _transactionDate,
 | 
			
		||||
				cashierId: cashierId,
 | 
			
		||||
				transactionId: transactionId,
 | 
			
		||||
				);
 | 
			
		||||
			print('Teks struk berhasil dihasilkan');
 | 
			
		||||
 | 
			
		||||
			// Tampilkan teks struk untuk debugging
 | 
			
		||||
			print('Isi teks struk:');
 | 
			
		||||
			print(strukText);
 | 
			
		||||
 | 
			
		||||
			// Konversi struk text ke format yang bisa dicetak
 | 
			
		||||
			final lines = strukText.split('\n');
 | 
			
		||||
			final List<LineText> list = lines
 | 
			
		||||
				.where((line) => line.isNotEmpty)
 | 
			
		||||
				.map((line) =>
 | 
			
		||||
						LineText(type: LineText.TYPE_TEXT, content: line, linefeed: 1))
 | 
			
		||||
				.toList();
 | 
			
		||||
			);
 | 
			
		||||
			
 | 
			
		||||
			// Tampilkan daftar baris untuk debugging
 | 
			
		||||
			print('Daftar baris yang akan dicetak:');
 | 
			
		||||
			for (int i = 0; i < list.length; i++) {
 | 
			
		||||
				print('Baris $i: ${list[i].content}');
 | 
			
		||||
			print('Byte array ESC/POS berhasil dihasilkan');
 | 
			
		||||
			print('Jumlah byte: ${bytes.length}');
 | 
			
		||||
 | 
			
		||||
			// Tampilkan byte array untuk debugging (dalam format hex)
 | 
			
		||||
			print('Isi byte array (hex):');
 | 
			
		||||
			if (bytes.length <= 1000) { // Batasi tampilan untuk mencegah output terlalu panjang
 | 
			
		||||
				print(bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' '));
 | 
			
		||||
			} else {
 | 
			
		||||
				print('Terlalu banyak byte untuk ditampilkan (${bytes.length} bytes)');
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Konfigurasi untuk mencetak struk - disesuaikan untuk printer termal umum
 | 
			
		||||
			Map<String, dynamic> config = {
 | 
			
		||||
				'width': 384, // Lebar kertas struk termal (dalam piksel) - 58mm printer
 | 
			
		||||
				'height': 800, // Tinggi kertas struk termal (dalam piksel)
 | 
			
		||||
				'gap': 0, // Jarak antar struk
 | 
			
		||||
			};
 | 
			
		||||
			print('Konfigurasi printer: $config');
 | 
			
		||||
 | 
			
		||||
			// Kirim perintah cetak ke printer
 | 
			
		||||
			print('Mengirim perintah cetak ke printer...');
 | 
			
		||||
			print('Jumlah baris yang akan dikirim: ${list.length}');
 | 
			
		||||
			
 | 
			
		||||
			// Verifikasi koneksi sebelum mencetak
 | 
			
		||||
			final isConnectedBeforePrint = await bluetoothPrint.isConnected ?? false;
 | 
			
		||||
			if (!isConnectedBeforePrint) {
 | 
			
		||||
| 
						 | 
				
			
			@ -430,16 +413,18 @@ class _ReceiptScreenState extends State<ReceiptScreen> {
 | 
			
		|||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			print('Mengirim perintah cetak dengan konfigurasi: $config');
 | 
			
		||||
			print('Mengirim byte array ke printer...');
 | 
			
		||||
			bool printSuccess = false;
 | 
			
		||||
			String printError = '';
 | 
			
		||||
			
 | 
			
		||||
			try {
 | 
			
		||||
				// Coba kirim perintah cetak beberapa kali jika gagal
 | 
			
		||||
				// Coba kirim byte array beberapa kali jika gagal
 | 
			
		||||
				for (int attempt = 1; attempt <= 3; attempt++) {
 | 
			
		||||
					print('Percobaan cetak ke-$attempt');
 | 
			
		||||
					try {
 | 
			
		||||
						await bluetoothPrint.printReceipt(config, list);
 | 
			
		||||
						// Konversi List<int> ke Uint8List
 | 
			
		||||
						final Uint8List data = Uint8List.fromList(bytes);
 | 
			
		||||
						await bluetoothPrint.printRawData(data);
 | 
			
		||||
						print('Perintah cetak berhasil dikirim pada percobaan ke-$attempt');
 | 
			
		||||
						printSuccess = true;
 | 
			
		||||
						break;
 | 
			
		||||
| 
						 | 
				
			
			@ -481,11 +466,11 @@ class _ReceiptScreenState extends State<ReceiptScreen> {
 | 
			
		|||
			print('Stack trace: $stackTrace');
 | 
			
		||||
			// Cetak detail error tambahan
 | 
			
		||||
			print('Tipe error: ${e.runtimeType}');
 | 
			
		||||
			// Menghapus baris bermasalah yang menggunakan e.message
 | 
			
		||||
			if (!mounted) return;
 | 
			
		||||
			ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
					SnackBar(content: Text('Gagal mencetak struk: $e')),
 | 
			
		||||
					);
 | 
			
		||||
						SnackBar(content: Text('Gagal mencetak struk: $e')),
 | 
			
		||||
						);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1090,48 +1075,52 @@ SpeedDialChild(
 | 
			
		|||
	      ),
 | 
			
		||||
// SpeedDialChild for PDF printing removed as per request
 | 
			
		||||
SpeedDialChild(
 | 
			
		||||
		child: const Icon(Icons.receipt),
 | 
			
		||||
		child: _isPrinting 
 | 
			
		||||
			? const CircularProgressIndicator(color: Colors.white, strokeWidth: 2) 
 | 
			
		||||
			: const Icon(Icons.receipt),
 | 
			
		||||
		label: 'Cetak Struk',
 | 
			
		||||
		onTap: () async {
 | 
			
		||||
			// Periksa koneksi secara real-time
 | 
			
		||||
			final isConnected = await _checkBluetoothConnection();
 | 
			
		||||
			if (isConnected) {
 | 
			
		||||
				_printToThermalPrinter();
 | 
			
		||||
			} else {
 | 
			
		||||
				// Coba sambungkan kembali jika ada device yang tersimpan
 | 
			
		||||
				if (_bluetoothDevice != null) {
 | 
			
		||||
					ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
						const SnackBar(
 | 
			
		||||
							content: Text('Mencoba menyambungkan ke printer...')),
 | 
			
		||||
					);
 | 
			
		||||
					try {
 | 
			
		||||
						await bluetoothPrint.connect(_bluetoothDevice!);
 | 
			
		||||
						// Tunggu sebentar untuk memastikan koneksi stabil
 | 
			
		||||
						await Future.delayed(const Duration(milliseconds: 500));
 | 
			
		||||
						// Periksa koneksi lagi
 | 
			
		||||
						final isConnectedAfterConnect = await _checkBluetoothConnection();
 | 
			
		||||
						if (isConnectedAfterConnect) {
 | 
			
		||||
							_printToThermalPrinter();
 | 
			
		||||
						} else {
 | 
			
		||||
		onTap: _isPrinting 
 | 
			
		||||
			? null 
 | 
			
		||||
			: () async {
 | 
			
		||||
				// Periksa koneksi secara real-time
 | 
			
		||||
				final isConnected = await _checkBluetoothConnection();
 | 
			
		||||
				if (isConnected) {
 | 
			
		||||
					_printToThermalPrinter();
 | 
			
		||||
				} else {
 | 
			
		||||
					// Coba sambungkan kembali jika ada device yang tersimpan
 | 
			
		||||
					if (_bluetoothDevice != null) {
 | 
			
		||||
						ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
							const SnackBar(
 | 
			
		||||
								content: Text('Mencoba menyambungkan ke printer...')),
 | 
			
		||||
						);
 | 
			
		||||
						try {
 | 
			
		||||
							await bluetoothPrint.connect(_bluetoothDevice!);
 | 
			
		||||
							// Tunggu sebentar untuk memastikan koneksi stabil
 | 
			
		||||
							await Future.delayed(const Duration(milliseconds: 500));
 | 
			
		||||
							// Periksa koneksi lagi
 | 
			
		||||
							final isConnectedAfterConnect = await _checkBluetoothConnection();
 | 
			
		||||
							if (isConnectedAfterConnect) {
 | 
			
		||||
								_printToThermalPrinter();
 | 
			
		||||
							} else {
 | 
			
		||||
								ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
									const SnackBar(
 | 
			
		||||
										content: Text('Gagal menyambungkan ke printer')),
 | 
			
		||||
								);
 | 
			
		||||
							}
 | 
			
		||||
						} catch (e) {
 | 
			
		||||
							ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
								const SnackBar(
 | 
			
		||||
									content: Text('Gagal menyambungkan ke printer')),
 | 
			
		||||
								SnackBar(
 | 
			
		||||
									content: Text('Gagal menyambungkan ke printer: $e')),
 | 
			
		||||
							);
 | 
			
		||||
						}
 | 
			
		||||
					} catch (e) {
 | 
			
		||||
					} else {
 | 
			
		||||
						ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
							SnackBar(
 | 
			
		||||
								content: Text('Gagal menyambungkan ke printer: $e')),
 | 
			
		||||
							const SnackBar(
 | 
			
		||||
								content: Text('Hubungkan printer terlebih dahulu')),
 | 
			
		||||
						);
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
						const SnackBar(
 | 
			
		||||
							content: Text('Hubungkan printer terlebih dahulu')),
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
			},
 | 
			
		||||
		backgroundColor: Colors.purple,
 | 
			
		||||
	),
 | 
			
		||||
],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ import 'package:cashumit/models/receipt_item.dart';
 | 
			
		|||
import 'package:flutter_esc_pos_utils/flutter_esc_pos_utils.dart';
 | 
			
		||||
import 'package:image/image.dart' as img;
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:ui' as ui;
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
 | 
			
		||||
/// Service untuk menghasilkan perintah ESC/POS menggunakan flutter_esc_pos_utils
 | 
			
		||||
class EscPosPrintService {
 | 
			
		||||
| 
						 | 
				
			
			@ -46,8 +48,16 @@ class EscPosPrintService {
 | 
			
		|||
      return "Rp ${formatter.format(amount)}";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Load capability profile
 | 
			
		||||
    final profile = await CapabilityProfile.load();
 | 
			
		||||
    // Load capability profile with timeout
 | 
			
		||||
    CapabilityProfile profile;
 | 
			
		||||
    try {
 | 
			
		||||
      profile = await CapabilityProfile.load();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Gagal memuat capability profile: $e');
 | 
			
		||||
      // Gunakan profile default jika gagal
 | 
			
		||||
      profile = await CapabilityProfile.load();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    final generator = Generator(PaperSize.mm58, profile);
 | 
			
		||||
    
 | 
			
		||||
    // Mulai dengan inisialisasi printer
 | 
			
		||||
| 
						 | 
				
			
			@ -56,18 +66,68 @@ class EscPosPrintService {
 | 
			
		|||
    // Tambahkan logo jika ada path-nya
 | 
			
		||||
    if (logoPath != null && logoPath.isNotEmpty) {
 | 
			
		||||
      try {
 | 
			
		||||
        // Membaca file gambar dari path lokal
 | 
			
		||||
        // Membaca file gambar dari path lokal dengan timeout
 | 
			
		||||
        final file = File(logoPath);
 | 
			
		||||
        if (await file.exists()) {
 | 
			
		||||
          final Uint8List imageBytes = await file.readAsBytes();
 | 
			
		||||
        bool fileExists = false;
 | 
			
		||||
        try {
 | 
			
		||||
          fileExists = await file.exists();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          print('Error saat memeriksa keberadaan file logo: $e');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (fileExists) {
 | 
			
		||||
          // Baca file dengan timeout
 | 
			
		||||
          Uint8List imageBytes;
 | 
			
		||||
          try {
 | 
			
		||||
            imageBytes = await file.readAsBytes();
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            print('Gagal membaca file logo: $e');
 | 
			
		||||
            throw Exception('Gagal membaca file logo');
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          // Decode gambar (format yang didukung: JPEG, PNG)
 | 
			
		||||
          final img.Image? image = img.decodeImage(imageBytes);
 | 
			
		||||
          if (image != null) {
 | 
			
		||||
            bytes += generator.image(image);
 | 
			
		||||
          // Decode gambar dengan penanganan error menggunakan isolate
 | 
			
		||||
          Uint8List? processedImageBytes;
 | 
			
		||||
          try {
 | 
			
		||||
            processedImageBytes = await compute(processImageInIsolate, ImageProcessParams(imageBytes, 200));
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            print('Gagal mendecode gambar: $e');
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          if (processedImageBytes != null) {
 | 
			
		||||
            // Decode the processed image bytes back to an image object for printing
 | 
			
		||||
            final img.Image? image = img.decodeImage(processedImageBytes);
 | 
			
		||||
            if (image != null) {
 | 
			
		||||
              bytes += generator.image(image);
 | 
			
		||||
            } else {
 | 
			
		||||
              // Jika gagal decode gambar, gunakan teks sebagai fallback
 | 
			
		||||
              bytes += generator.text(storeName,
 | 
			
		||||
                  styles: PosStyles(
 | 
			
		||||
                    bold: true,
 | 
			
		||||
                    height: PosTextSize.size1,
 | 
			
		||||
                    width: PosTextSize.size1,
 | 
			
		||||
                    align: PosAlign.center,
 | 
			
		||||
                  ));
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            // Jika gagal decode gambar, gunakan teks sebagai fallback
 | 
			
		||||
            bytes += generator.text(storeName,
 | 
			
		||||
                styles: PosStyles(
 | 
			
		||||
                  bold: true,
 | 
			
		||||
                  height: PosTextSize.size1,
 | 
			
		||||
                  width: PosTextSize.size1,
 | 
			
		||||
                  align: PosAlign.center,
 | 
			
		||||
                ));
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          throw Exception('File logo tidak ditemukan');
 | 
			
		||||
          print('File logo tidak ditemukan: $logoPath');
 | 
			
		||||
          // Jika file tidak ditemukan, gunakan teks sebagai fallback
 | 
			
		||||
          bytes += generator.text(storeName,
 | 
			
		||||
              styles: PosStyles(
 | 
			
		||||
                bold: true,
 | 
			
		||||
                height: PosTextSize.size1,
 | 
			
		||||
                width: PosTextSize.size1,
 | 
			
		||||
                align: PosAlign.center,
 | 
			
		||||
              ));
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Gagal memuat logo: $e');
 | 
			
		||||
| 
						 | 
				
			
			@ -91,157 +151,244 @@ class EscPosPrintService {
 | 
			
		|||
          ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    bytes += generator.text(storeAddress,
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    bytes += generator.text('Admin: $adminName',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    bytes += generator.text('Telp: $adminPhone',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    bytes += generator.text('$formattedDate',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    
 | 
			
		||||
    bytes += generator.feed(1);
 | 
			
		||||
    
 | 
			
		||||
    // Garis pemisah
 | 
			
		||||
    bytes += generator.text('================================',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    
 | 
			
		||||
    // Informasi transaksi
 | 
			
		||||
    bytes += generator.text('Kasir: $cashierId',
 | 
			
		||||
        styles: PosStyles(bold: true));
 | 
			
		||||
    bytes += generator.text('ID Transaksi: $transactionId',
 | 
			
		||||
        styles: PosStyles());
 | 
			
		||||
    
 | 
			
		||||
    bytes += generator.feed(1);
 | 
			
		||||
    
 | 
			
		||||
    // Garis pemisah
 | 
			
		||||
    bytes += generator.text('================================',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    
 | 
			
		||||
    // Tabel item
 | 
			
		||||
    bytes += generator.row([
 | 
			
		||||
      PosColumn(
 | 
			
		||||
        text: 'Item',
 | 
			
		||||
        width: 7,
 | 
			
		||||
        styles: PosStyles(bold: true, align: PosAlign.left),
 | 
			
		||||
      ),
 | 
			
		||||
      PosColumn(
 | 
			
		||||
        text: 'Total',
 | 
			
		||||
        width: 5,
 | 
			
		||||
        styles: PosStyles(bold: true, align: PosAlign.right),
 | 
			
		||||
      ),
 | 
			
		||||
    ]);
 | 
			
		||||
    
 | 
			
		||||
    // Item list
 | 
			
		||||
    print('Memulai iterasi item...');
 | 
			
		||||
    for (int i = 0; i < items.length; i++) {
 | 
			
		||||
      var item = items[i];
 | 
			
		||||
      print('Item $i: ${item.description}, qty: ${item.quantity}, price: ${item.price}, total: ${item.total}');
 | 
			
		||||
    try {
 | 
			
		||||
      bytes += generator.text(storeAddress,
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      bytes += generator.text('Admin: $adminName',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      bytes += generator.text('Telp: $adminPhone',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      bytes += generator.text('$formattedDate',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      
 | 
			
		||||
      // Untuk item dengan detail kuantitas dan harga
 | 
			
		||||
      bytes += generator.feed(1);
 | 
			
		||||
      
 | 
			
		||||
      // Garis pemisah
 | 
			
		||||
      bytes += generator.text('================================',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      
 | 
			
		||||
      // Informasi transaksi
 | 
			
		||||
      bytes += generator.text('Kasir: $cashierId',
 | 
			
		||||
          styles: PosStyles(bold: true));
 | 
			
		||||
      bytes += generator.text('ID Transaksi: $transactionId',
 | 
			
		||||
          styles: PosStyles());
 | 
			
		||||
      
 | 
			
		||||
      bytes += generator.feed(1);
 | 
			
		||||
      
 | 
			
		||||
      // Garis pemisah
 | 
			
		||||
      bytes += generator.text('================================',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      
 | 
			
		||||
      // Tabel item
 | 
			
		||||
      bytes += generator.row([
 | 
			
		||||
        PosColumn(
 | 
			
		||||
          text: item.description,
 | 
			
		||||
          width: 12,
 | 
			
		||||
          styles: PosStyles(align: PosAlign.left),
 | 
			
		||||
        ),
 | 
			
		||||
      ]);
 | 
			
		||||
      
 | 
			
		||||
      bytes += generator.row([
 | 
			
		||||
        PosColumn(
 | 
			
		||||
          text: '${item.quantity} x ${formatRupiah(item.price)}',
 | 
			
		||||
          text: 'Item',
 | 
			
		||||
          width: 7,
 | 
			
		||||
          styles: PosStyles(align: PosAlign.left, height: PosTextSize.size1, width: PosTextSize.size1),
 | 
			
		||||
          styles: PosStyles(bold: true, align: PosAlign.left),
 | 
			
		||||
        ),
 | 
			
		||||
        PosColumn(
 | 
			
		||||
          text: formatRupiah(item.total),
 | 
			
		||||
          text: 'Total',
 | 
			
		||||
          width: 5,
 | 
			
		||||
          styles: PosStyles(align: PosAlign.right),
 | 
			
		||||
          styles: PosStyles(bold: true, align: PosAlign.right),
 | 
			
		||||
        ),
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
    print('Selesai iterasi item');
 | 
			
		||||
    
 | 
			
		||||
    // Garis pemisah sebelum total
 | 
			
		||||
    bytes += generator.text('--------------------------------',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    
 | 
			
		||||
    // Total
 | 
			
		||||
    final totalAmount = items.fold(0.0, (sum, item) => sum + item.total);
 | 
			
		||||
    print('Total amount: $totalAmount');
 | 
			
		||||
    
 | 
			
		||||
    bytes += generator.row([
 | 
			
		||||
      PosColumn(
 | 
			
		||||
        text: 'TOTAL',
 | 
			
		||||
        width: 7,
 | 
			
		||||
        styles: PosStyles(bold: true, align: PosAlign.left),
 | 
			
		||||
      ),
 | 
			
		||||
      PosColumn(
 | 
			
		||||
        text: formatRupiah(totalAmount),
 | 
			
		||||
        width: 5,
 | 
			
		||||
        styles: PosStyles(bold: true, align: PosAlign.right),
 | 
			
		||||
      ),
 | 
			
		||||
    ]);
 | 
			
		||||
    
 | 
			
		||||
    // Garis pemisah setelah total
 | 
			
		||||
    bytes += generator.text('================================',
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
 | 
			
		||||
    // Memuat teks kustom dari shared preferences
 | 
			
		||||
    final customDisclaimer = prefs.getString('store_disclaimer_text') ?? 
 | 
			
		||||
        'Barang yang sudah dibeli tidak dapat dikembalikan/ditukar. '
 | 
			
		||||
        'Harap periksa kembali struk belanja Anda sebelum meninggalkan toko.';
 | 
			
		||||
    final customThankYou = prefs.getString('thank_you_text') ?? '*** TERIMA KASIH ***';
 | 
			
		||||
    final customPantun = prefs.getString('pantun_text') ?? 
 | 
			
		||||
        'Belanja di toko kami, hemat dan nyaman,\n'
 | 
			
		||||
        'Dengan penuh semangat, kami siap melayani,\n'
 | 
			
		||||
        'Harapan kami, Anda selalu puas,\n'
 | 
			
		||||
        'Sampai jumpa lagi, selamat tinggal.';
 | 
			
		||||
 | 
			
		||||
    // Menambahkan disclaimer
 | 
			
		||||
    bytes += generator.feed(1);
 | 
			
		||||
    
 | 
			
		||||
    // Memecah disclaimer menjadi beberapa baris jika terlalu panjang
 | 
			
		||||
    final disclaimerLines = customDisclaimer.split(' ');
 | 
			
		||||
    final List<String> wrappedDisclaimer = [];
 | 
			
		||||
    String currentLine = '';
 | 
			
		||||
 | 
			
		||||
    for (final word in disclaimerLines) {
 | 
			
		||||
      if ((currentLine + word).length > 32) {
 | 
			
		||||
        wrappedDisclaimer.add(currentLine.trim());
 | 
			
		||||
        currentLine = word + ' ';
 | 
			
		||||
      } else {
 | 
			
		||||
        currentLine += word + ' ';
 | 
			
		||||
      
 | 
			
		||||
      // Item list dengan penanganan error
 | 
			
		||||
      print('Memulai iterasi item...');
 | 
			
		||||
      for (int i = 0; i < items.length; i++) {
 | 
			
		||||
        try {
 | 
			
		||||
          var item = items[i];
 | 
			
		||||
          print('Item $i: ${item.description}, qty: ${item.quantity}, price: ${item.price}, total: ${item.total}');
 | 
			
		||||
          
 | 
			
		||||
          // Untuk item dengan detail kuantitas dan harga
 | 
			
		||||
          bytes += generator.row([
 | 
			
		||||
            PosColumn(
 | 
			
		||||
              text: item.description,
 | 
			
		||||
              width: 12,
 | 
			
		||||
              styles: PosStyles(align: PosAlign.left),
 | 
			
		||||
            ),
 | 
			
		||||
          ]);
 | 
			
		||||
          
 | 
			
		||||
          bytes += generator.row([
 | 
			
		||||
            PosColumn(
 | 
			
		||||
              text: '${item.quantity} x ${formatRupiah(item.price)}',
 | 
			
		||||
              width: 7,
 | 
			
		||||
              styles: PosStyles(align: PosAlign.left, height: PosTextSize.size1, width: PosTextSize.size1),
 | 
			
		||||
            ),
 | 
			
		||||
            PosColumn(
 | 
			
		||||
              text: formatRupiah(item.total),
 | 
			
		||||
              width: 5,
 | 
			
		||||
              styles: PosStyles(align: PosAlign.right),
 | 
			
		||||
            ),
 | 
			
		||||
          ]);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          print('Error saat memproses item $i: $e');
 | 
			
		||||
          // Lanjutkan ke item berikutnya jika ada error
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (currentLine.trim().isNotEmpty) {
 | 
			
		||||
      wrappedDisclaimer.add(currentLine.trim());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (final line in wrappedDisclaimer) {
 | 
			
		||||
      bytes += generator.text(line,
 | 
			
		||||
      print('Selesai iterasi item');
 | 
			
		||||
      
 | 
			
		||||
      // Garis pemisah sebelum total
 | 
			
		||||
      bytes += generator.text('--------------------------------',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Menambahkan ucapan terima kasih
 | 
			
		||||
    bytes += generator.feed(1);
 | 
			
		||||
    bytes += generator.text(customThankYou,
 | 
			
		||||
        styles: PosStyles(align: PosAlign.center, bold: true));
 | 
			
		||||
 | 
			
		||||
    // Menambahkan pantun
 | 
			
		||||
    bytes += generator.feed(1);
 | 
			
		||||
    final pantunLines = customPantun.split('\n');
 | 
			
		||||
    for (final line in pantunLines) {
 | 
			
		||||
      bytes += generator.text(line,
 | 
			
		||||
      
 | 
			
		||||
      // Total
 | 
			
		||||
      double totalAmount = 0.0;
 | 
			
		||||
      try {
 | 
			
		||||
        totalAmount = items.fold(0.0, (sum, item) => sum + item.total);
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Error saat menghitung total: $e');
 | 
			
		||||
        totalAmount = 0.0;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      print('Total amount: $totalAmount');
 | 
			
		||||
      
 | 
			
		||||
      bytes += generator.row([
 | 
			
		||||
        PosColumn(
 | 
			
		||||
          text: 'TOTAL',
 | 
			
		||||
          width: 7,
 | 
			
		||||
          styles: PosStyles(bold: true, align: PosAlign.left),
 | 
			
		||||
        ),
 | 
			
		||||
        PosColumn(
 | 
			
		||||
          text: formatRupiah(totalAmount),
 | 
			
		||||
          width: 5,
 | 
			
		||||
          styles: PosStyles(bold: true, align: PosAlign.right),
 | 
			
		||||
        ),
 | 
			
		||||
      ]);
 | 
			
		||||
      
 | 
			
		||||
      // Garis pemisah setelah total
 | 
			
		||||
      bytes += generator.text('================================',
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Spasi di akhir dan pemotongan kertas
 | 
			
		||||
    bytes += generator.feed(2);
 | 
			
		||||
    bytes += generator.cut();
 | 
			
		||||
      // Memuat teks kustom dari shared preferences
 | 
			
		||||
      String customDisclaimer;
 | 
			
		||||
      try {
 | 
			
		||||
        customDisclaimer = prefs.getString('store_disclaimer_text') ?? 
 | 
			
		||||
            'Barang yang sudah dibeli tidak dapat dikembalikan/ditukar. '
 | 
			
		||||
            'Harap periksa kembali struk belanja Anda sebelum meninggalkan toko.';
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Error saat memuat disclaimer: $e');
 | 
			
		||||
        customDisclaimer = 'Barang yang sudah dibeli tidak dapat dikembalikan/ditukar.';
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      String customThankYou;
 | 
			
		||||
      try {
 | 
			
		||||
        customThankYou = prefs.getString('thank_you_text') ?? '*** TERIMA KASIH ***';
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Error saat memuat thank you text: $e');
 | 
			
		||||
        customThankYou = '*** TERIMA KASIH ***';
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      String customPantun;
 | 
			
		||||
      try {
 | 
			
		||||
        customPantun = prefs.getString('pantun_text') ?? 
 | 
			
		||||
            'Belanja di toko kami, hemat dan nyaman\n'
 | 
			
		||||
            'Dengan penuh semangat, kami siap melayani\n'
 | 
			
		||||
            'Harapan kami, Anda selalu puas\n'
 | 
			
		||||
            'Sampai jumpa lagi, selamat tinggal.';
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Error saat memuat pantun: $e');
 | 
			
		||||
        customPantun = '';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Menambahkan disclaimer
 | 
			
		||||
      bytes += generator.feed(1);
 | 
			
		||||
      
 | 
			
		||||
      // Memecah disclaimer menjadi beberapa baris jika terlalu panjang
 | 
			
		||||
      try {
 | 
			
		||||
        final disclaimerLines = customDisclaimer.split(' ');
 | 
			
		||||
        final List<String> wrappedDisclaimer = [];
 | 
			
		||||
        String currentLine = '';
 | 
			
		||||
 | 
			
		||||
        for (final word in disclaimerLines) {
 | 
			
		||||
          if ((currentLine + word).length > 32) {
 | 
			
		||||
            wrappedDisclaimer.add(currentLine.trim());
 | 
			
		||||
            currentLine = word + ' ';
 | 
			
		||||
          } else {
 | 
			
		||||
            currentLine += word + ' ';
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (currentLine.trim().isNotEmpty) {
 | 
			
		||||
          wrappedDisclaimer.add(currentLine.trim());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (final line in wrappedDisclaimer) {
 | 
			
		||||
          bytes += generator.text(line,
 | 
			
		||||
              styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Error saat memproses disclaimer: $e');
 | 
			
		||||
        // Fallback jika ada error
 | 
			
		||||
        bytes += generator.text(customDisclaimer,
 | 
			
		||||
            styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Menambahkan ucapan terima kasih
 | 
			
		||||
      bytes += generator.feed(1);
 | 
			
		||||
      bytes += generator.text(customThankYou,
 | 
			
		||||
          styles: PosStyles(align: PosAlign.center, bold: true));
 | 
			
		||||
 | 
			
		||||
      // Menambahkan pantun jika ada
 | 
			
		||||
      if (customPantun.isNotEmpty) {
 | 
			
		||||
        try {
 | 
			
		||||
          bytes += generator.feed(1);
 | 
			
		||||
          final pantunLines = customPantun.split('\n');
 | 
			
		||||
          for (final line in pantunLines) {
 | 
			
		||||
            bytes += generator.text(line,
 | 
			
		||||
                styles: PosStyles(align: PosAlign.center));
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          print('Error saat menambahkan pantun: $e');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Spasi di akhir dan pemotongan kertas
 | 
			
		||||
      bytes += generator.feed(2);
 | 
			
		||||
      bytes += generator.cut();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Error saat membuat bagian akhir struk: $e');
 | 
			
		||||
      // Pastikan struk tetap dipotong meski ada error
 | 
			
		||||
      bytes += generator.feed(2);
 | 
			
		||||
      bytes += generator.cut();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    print('Jumlah byte yang dihasilkan: ${bytes.length}');
 | 
			
		||||
    
 | 
			
		||||
    return bytes;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Data class to hold image processing parameters
 | 
			
		||||
class ImageProcessParams {
 | 
			
		||||
  final Uint8List imageBytes;
 | 
			
		||||
  final int maxWidth;
 | 
			
		||||
  
 | 
			
		||||
  ImageProcessParams(this.imageBytes, this.maxWidth);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Helper function to decode and resize image in an isolate
 | 
			
		||||
Uint8List? processImageInIsolate(ImageProcessParams params) {
 | 
			
		||||
  try {
 | 
			
		||||
    // Decode image
 | 
			
		||||
    img.Image? image = img.decodeImage(params.imageBytes);
 | 
			
		||||
    
 | 
			
		||||
    if (image != null) {
 | 
			
		||||
      // Resize gambar agar sesuai dengan ukuran kertas printer
 | 
			
		||||
      if (image.width > params.maxWidth) {
 | 
			
		||||
        final ratio = params.maxWidth / image.width;
 | 
			
		||||
        final newHeight = (image.height * ratio).round();
 | 
			
		||||
        image = img.copyResize(image, width: params.maxWidth, height: newHeight);
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Convert image to bytes that can be sent back to main isolate
 | 
			
		||||
      final imageBytes = Uint8List.fromList(img.encodePng(image));
 | 
			
		||||
      return imageBytes;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return null;
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    print('Error processing image in isolate: $e');
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +17,25 @@ class StrukTextGenerator {
 | 
			
		|||
    return char * width;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Fungsi untuk membuat representasi ASCII sederhana dari logo
 | 
			
		||||
  static String createAsciiLogo(String storeName, int width) {
 | 
			
		||||
    final buffer = StringBuffer();
 | 
			
		||||
    
 | 
			
		||||
    // Membuat logo ASCII sederhana dengan nama toko
 | 
			
		||||
    final int nameLength = storeName.length;
 | 
			
		||||
    final int boxWidth = nameLength + 4;
 | 
			
		||||
    final String horizontalLine = '=' * boxWidth;
 | 
			
		||||
    final String emptyLine = '|${' ' * (boxWidth - 2)}|';
 | 
			
		||||
    
 | 
			
		||||
    buffer.writeln(centerText(horizontalLine, width));
 | 
			
		||||
    buffer.writeln(centerText(emptyLine, width));
 | 
			
		||||
    buffer.writeln(centerText('| $storeName |', width));
 | 
			
		||||
    buffer.writeln(centerText(emptyLine, width));
 | 
			
		||||
    buffer.writeln(centerText(horizontalLine, width));
 | 
			
		||||
    
 | 
			
		||||
    return buffer.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Menghasilkan struk dalam format teks berdasarkan data transaksi
 | 
			
		||||
  static Future<String> generateStrukText({
 | 
			
		||||
    required List<ReceiptItem> items,
 | 
			
		||||
| 
						 | 
				
			
			@ -36,11 +55,13 @@ class StrukTextGenerator {
 | 
			
		|||
    final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
 | 
			
		||||
    final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
 | 
			
		||||
    final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
 | 
			
		||||
    final logoPath = prefs.getString('store_logo_path'); // Load logo path
 | 
			
		||||
    
 | 
			
		||||
    print('Nama toko: $storeName');
 | 
			
		||||
    print('Alamat toko: $storeAddress');
 | 
			
		||||
    print('Nama admin: $adminName');
 | 
			
		||||
    print('Telepon admin: $adminPhone');
 | 
			
		||||
    print('Logo path: $logoPath');
 | 
			
		||||
    
 | 
			
		||||
    // Format tanggal
 | 
			
		||||
    final dateFormatter = DateFormat('dd/MM/yyyy');
 | 
			
		||||
| 
						 | 
				
			
			@ -56,21 +77,17 @@ class StrukTextGenerator {
 | 
			
		|||
    // Bangun struk dalam format teks
 | 
			
		||||
    final buffer = StringBuffer();
 | 
			
		||||
    
 | 
			
		||||
    // Header toko - menggunakan lebar 32 karakter untuk kompatibilitas printer termal
 | 
			
		||||
    buffer.writeln(centerText(storeName, 32));
 | 
			
		||||
    // Header toko dengan logo ASCII - menggunakan lebar 32 karakter untuk kompatibilitas printer termal
 | 
			
		||||
    buffer.write(createAsciiLogo(storeName, 32));
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
    buffer.writeln(centerText(storeAddress, 32));
 | 
			
		||||
    buffer.writeln(centerText('Admin: $adminName', 32));
 | 
			
		||||
    buffer.writeln(centerText('Telp: $adminPhone', 32));
 | 
			
		||||
    buffer.writeln(createSeparator(32, '='));
 | 
			
		||||
    
 | 
			
		||||
    // Info transaksi
 | 
			
		||||
    buffer.writeln('TANGGAL : $formattedDate');
 | 
			
		||||
    buffer.writeln('DARI    : Kas Tunai');
 | 
			
		||||
    buffer.writeln('KE      : Pendapatan Harian');
 | 
			
		||||
    buffer.writeln(centerText(' $formattedDate ', 32));
 | 
			
		||||
    buffer.writeln(createSeparator(32, '='));
 | 
			
		||||
    
 | 
			
		||||
    // Header tabel - disesuaikan lebar kolom untuk printer termal
 | 
			
		||||
    buffer.writeln('ITEM            QTY HARGA  TOTAL');
 | 
			
		||||
    buffer.writeln('ITEM        QTY   HARGA    TOTAL');
 | 
			
		||||
    buffer.writeln(createSeparator(32, '-'));
 | 
			
		||||
    
 | 
			
		||||
    // Item list
 | 
			
		||||
| 
						 | 
				
			
			@ -107,12 +124,52 @@ class StrukTextGenerator {
 | 
			
		|||
    
 | 
			
		||||
    // Garis pemisah setelah total
 | 
			
		||||
    buffer.writeln(createSeparator(32, '='));
 | 
			
		||||
    
 | 
			
		||||
    // Footer
 | 
			
		||||
    buffer.writeln(centerText('*** TERIMA KASIH ***', 32));
 | 
			
		||||
    buffer.writeln(centerText('Barang yang sudah dibeli', 32));
 | 
			
		||||
    buffer.writeln(centerText('tidak dapat dikembalikan', 32));
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Memuat teks kustom dari shared preferences
 | 
			
		||||
    final customDisclaimer = prefs.getString('store_disclaimer_text') ?? 
 | 
			
		||||
        'Barang yang sudah dibeli tidak dapat dikembalikan/ditukar. '
 | 
			
		||||
        'Harap periksa kembali struk belanja Anda sebelum meninggalkan toko.';
 | 
			
		||||
    final customThankYou = prefs.getString('thank_you_text') ?? '*** TERIMA KASIH ***';
 | 
			
		||||
    final customPantun = prefs.getString('pantun_text') ?? 
 | 
			
		||||
        'Belanja di toko kami, hemat dan nyaman,\n'
 | 
			
		||||
        'Dengan penuh semangat, kami siap melayani,\n'
 | 
			
		||||
        'Harapan kami, Anda selalu puas,\n'
 | 
			
		||||
        'Sampai jumpa lagi, selamat tinggal.';
 | 
			
		||||
 | 
			
		||||
    // Menambahkan disclaimer
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
    // Memecah disclaimer menjadi beberapa baris jika terlalu panjang
 | 
			
		||||
    final disclaimerLines = customDisclaimer.split(' ');
 | 
			
		||||
    final List<String> wrappedDisclaimer = [];
 | 
			
		||||
    String currentLine = '';
 | 
			
		||||
 | 
			
		||||
    for (final word in disclaimerLines) {
 | 
			
		||||
      if ((currentLine + word).length > 32) {
 | 
			
		||||
        wrappedDisclaimer.add(currentLine.trim());
 | 
			
		||||
        currentLine = word + ' ';
 | 
			
		||||
      } else {
 | 
			
		||||
        currentLine += word + ' ';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (currentLine.trim().isNotEmpty) {
 | 
			
		||||
      wrappedDisclaimer.add(currentLine.trim());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (final line in wrappedDisclaimer) {
 | 
			
		||||
      buffer.writeln(centerText(line, 32));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Menambahkan ucapan terima kasih
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
    buffer.writeln(centerText(customThankYou, 32));
 | 
			
		||||
 | 
			
		||||
    // Menambahkan pantun
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
    final pantunLines = customPantun.split('\n');
 | 
			
		||||
    for (final line in pantunLines) {
 | 
			
		||||
      buffer.writeln(centerText(line, 32));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Spasi di akhir
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
| 
						 | 
				
			
			@ -123,4 +180,4 @@ class StrukTextGenerator {
 | 
			
		|||
    
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -216,6 +216,9 @@ public class BluetoothPrintPlugin implements FlutterPlugin, ActivityAware, Metho
 | 
			
		|||
      case "printTest":
 | 
			
		||||
        printTest(result);
 | 
			
		||||
        break;
 | 
			
		||||
      case "printRawData":
 | 
			
		||||
        printRawData(call, result);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        result.notImplemented();
 | 
			
		||||
        break;
 | 
			
		||||
| 
						 | 
				
			
			@ -402,6 +405,60 @@ public class BluetoothPrintPlugin implements FlutterPlugin, ActivityAware, Metho
 | 
			
		|||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("unchecked")
 | 
			
		||||
  private void printRawData(MethodCall call, Result result) {
 | 
			
		||||
    Map<String, Object> args = call.arguments();
 | 
			
		||||
 | 
			
		||||
    // Tambahkan logging untuk debugging
 | 
			
		||||
    Log.d(TAG, "******************* Memulai proses printRawData, curMacAddress: " + curMacAddress);
 | 
			
		||||
    
 | 
			
		||||
    // Periksa apakah curMacAddress sudah diatur
 | 
			
		||||
    if (curMacAddress == null || curMacAddress.isEmpty()) {
 | 
			
		||||
      Log.e(TAG, "******************* curMacAddress tidak diatur, tidak bisa mencetak");
 | 
			
		||||
      result.error("not connected", "Printer address not set", null);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final DeviceConnFactoryManager deviceConnFactoryManager = DeviceConnFactoryManager.getDeviceConnFactoryManagers().get(curMacAddress);
 | 
			
		||||
    if (deviceConnFactoryManager == null) {
 | 
			
		||||
      Log.e(TAG, "******************* deviceConnFactoryManager tidak ditemukan untuk alamat: " + curMacAddress);
 | 
			
		||||
      result.error("not connected", "Device connection manager not found", null);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Periksa status koneksi
 | 
			
		||||
    if (!deviceConnFactoryManager.getConnState()) {
 | 
			
		||||
      Log.e(TAG, "******************* Printer tidak terhubung, status koneksi: " + deviceConnFactoryManager.getConnState());
 | 
			
		||||
      result.error("not connected", "Printer not connected", null);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (args != null && args.containsKey("data")) {
 | 
			
		||||
      final byte[] data = (byte[]) args.get("data");
 | 
			
		||||
      if(data == null || data.length == 0){
 | 
			
		||||
        Log.e(TAG, "******************* Data untuk dicetak null atau kosong");
 | 
			
		||||
        result.error("no data", "Data to print is null or empty", null);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Log.d(TAG, "******************* Mengirim data raw untuk dicetak, jumlah byte: " + data.length);
 | 
			
		||||
      
 | 
			
		||||
      threadPool = ThreadPool.getInstantiation();
 | 
			
		||||
      threadPool.addSerialTask(new Runnable() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void run() {
 | 
			
		||||
          assert deviceConnFactoryManager != null;
 | 
			
		||||
          deviceConnFactoryManager.sendByteDataImmediately(data);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      result.success(true);
 | 
			
		||||
    }else{
 | 
			
		||||
      Log.e(TAG, "******************* Data tidak ditemukan");
 | 
			
		||||
      result.error("please add data", "", null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("unchecked")
 | 
			
		||||
  private void print(MethodCall call, Result result) {
 | 
			
		||||
    Map<String, Object> args = call.arguments();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -156,4 +156,8 @@ class BluetoothPrint {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  Future<dynamic> printTest() => _channel.invokeMethod('printTest');
 | 
			
		||||
 | 
			
		||||
  Future<dynamic> printRawData(Uint8List data) {
 | 
			
		||||
    return _channel.invokeMethod('printRawData', {'data': data});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,8 @@ dependencies:
 | 
			
		|||
  http_auth: ^1.0.4
 | 
			
		||||
  bluetooth_print:
 | 
			
		||||
    path: ./plugins
 | 
			
		||||
  flutter_esc_pos_utils: ^0.0.1
 | 
			
		||||
  image: ^3.0.1
 | 
			
		||||
  intl: ^0.20.2
 | 
			
		||||
  pdf: ^3.8.1
 | 
			
		||||
  path_provider: ^2.0.14
 | 
			
		||||
| 
						 | 
				
			
			@ -71,9 +73,8 @@ flutter:
 | 
			
		|||
  uses-material-design: true
 | 
			
		||||
 | 
			
		||||
  # To add assets to your application, add an assets section, like this:
 | 
			
		||||
  # assets:
 | 
			
		||||
  #   - images/a_dot_burr.jpeg
 | 
			
		||||
  #   - images/a_dot_ham.jpeg
 | 
			
		||||
  assets:
 | 
			
		||||
    - assets/images/
 | 
			
		||||
 | 
			
		||||
  # An image asset can refer to one or more resolution-specific "variants", see
 | 
			
		||||
  # https://flutter.dev/to/resolution-aware-images
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue