import 'dart:developer'; import 'package:dartz/dartz.dart' hide Order; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart'; import 'package:injectable/injectable.dart' hide Order; import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; import '../../../domain/order/order.dart'; import '../../../domain/outlet/outlet.dart'; import '../../../domain/printer/printer.dart'; import '../../../presentation/components/print/print_ui.dart'; import '../../auth/datasources/local_data_provider.dart'; import '../../outlet/datasources/local_data_provider.dart'; import '../datasource/local_data_provider.dart'; import '../printer_dtos.dart'; @Injectable(as: IPrinterRepository) class PrinterRepository implements IPrinterRepository { final PrinterLocalDataProvider _localDataProvider; final OutletLocalDatasource _outletLocalDatasource; final AuthLocalDataProvider _authLocalDataProvider; final _logName = 'PrinterRepository'; PrinterRepository( this._localDataProvider, this._outletLocalDatasource, this._authLocalDataProvider, ); @override Future> connectBluetooth( String macAddress, ) async { try { bool isConnected = await PrintBluetoothThermal.connectionStatus; if (isConnected) { log("Already connected to Bluetooth printer", name: _logName); return right(true); } bool connected = await PrintBluetoothThermal.connect( macPrinterAddress: macAddress, ); if (connected) { log( "Successfully connected to Bluetooth printer: $macAddress", name: _logName, ); } else { FirebaseCrashlytics.instance.recordError( 'Failed to connect to Bluetooth printer', null, reason: 'Failed to connect to Bluetooth printe', information: [ 'function: connectBluetoothPrinter(String macAddress)', 'macAddress: $macAddress', ], ); log( "Failed to connect to Bluetooth printer: $macAddress", name: _logName, ); } return right(connected); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( e, stackTrace, reason: 'Error connecting to Bluetooth printer', information: [ 'function: connectBluetoothPrinter(String macAddress)', 'Printer: Bluetooth printer', 'macAddress: $macAddress', ], ); log("Error connecting to Bluetooth printer", name: _logName, error: e); return left( PrinterFailure.dynamicErrorMessage( 'Error connecting to Bluetooth printer', ), ); } } @override Future> disconectBluetooth() async { try { bool result = await PrintBluetoothThermal.disconnect; log("Bluetooth printer disconnected: $result", name: _logName); return right(result); } catch (e) { log("Error disconnecting Bluetooth printer", error: e, name: _logName); return left( PrinterFailure.dynamicErrorMessage( 'Error disconnecting Bluetooth printer', ), ); } } @override Future>> getPairedBluetoothDevices() async { try { final result = await PrintBluetoothThermal.pairedBluetooths; log("Paired Bluetooth devices: $result", name: _logName); return right(result); } catch (e) { log("Error getting paired Bluetooth devices", name: _logName, error: e); return left( PrinterFailure.dynamicErrorMessage( 'Error getting paired Bluetooth devices', ), ); } } @override Future> isBluetoothEnabled() async { try { final result = await PrintBluetoothThermal.bluetoothEnabled; return right(result); } catch (e) { log("Error checking Bluetooth status", name: _logName, error: e); return left( PrinterFailure.dynamicErrorMessage('Error checking Bluetooth status'), ); } } @override Future> createPrinter(Printer printer) async { try { final result = await _localDataProvider.createPrinter( PrinterDto.fromDomain(printer), ); if (result.hasError) { return left(result.error!); } return right(unit); } catch (e) { log('createPrinterError', name: _logName, error: e); return left(const PrinterFailure.unexpectedError()); } } @override Future> deletePrinter(int id) async { try { final result = await _localDataProvider.deletePrinter(id); if (result.hasError) { return left(result.error!); } return right(unit); } catch (e) { log('deletePrinterError', name: _logName, error: e); return left(const PrinterFailure.unexpectedError()); } } @override Future> getPrinterByCode(String code) async { try { final result = await _localDataProvider.findPrinterByCode(code); if (result.hasError) { return left(result.error!); } final printer = result.data!.toDomain(); return right(printer); } catch (e) { log('getPrinterByCodeError', name: _logName, error: e); return left(const PrinterFailure.unexpectedError()); } } @override Future> updatePrinter( Printer printer, int id, ) async { try { final result = await _localDataProvider.updatePrinter( PrinterDto.fromDomain(printer), id, ); if (result.hasError) { return left(result.error!); } return right(unit); } catch (e) { log('updatePrinterError', name: _logName, error: e); return left(const PrinterFailure.unexpectedError()); } } @override Future> printStruct( Printer printer, List printData, ) async { try { log(printer.toString()); if (printer.type == 'Bluetooth') { final connected = await connectBluetooth(printer.address); connected.fold( (f) { return left( PrinterFailure.dynamicErrorMessage( 'Printer cannot connect to bluetooth, Please connect in printer setting!', ), ); }, (connect) async { if (!connect) { FirebaseCrashlytics.instance.recordError( 'Failed to connect to Bluetooth printer', null, reason: 'Failed to connect to Bluetooth print', information: [ 'function: printStruct(Printer printer, List printValue,)', 'Printer: ${printer.name}', 'macAddress: ${printer.address}', 'in: $_logName ', ], ); return left( PrinterFailure.dynamicErrorMessage( 'Printer cannot connect to bluetooth, Please connect in printer setting!', ), ); } bool printResult = await _printBluetooth(printData); if (!printResult) { FirebaseCrashlytics.instance.recordError( 'Failed to print to ${printer.name}', null, information: [ 'function: await printBluetooth(printData);', 'print: $printData', 'in: $_logName', ], ); return left( PrinterFailure.dynamicErrorMessage( 'Failed to print to ${printer.name}', ), ); } return right(printResult); }, ); } else { bool printResult = await _printNetwork(printer.address, printData); if (!printResult) { FirebaseCrashlytics.instance.recordError( 'Failed to connect to Network Printer', null, reason: 'Failed to connect to Network Printer', information: [ 'function: await printNetwork(printer.address, printData);', 'Printer: ${printer.name}', 'ipAddress: ${printer.address}', 'print: $printData', 'in: $_logName', ], ); } return right(printResult); } return right(false); } catch (e) { log("Error printing struct", name: _logName, error: e); return left(PrinterFailure.dynamicErrorMessage('Error printing struct')); } } Future _printBluetooth(List printData) async { try { bool isConnected = await PrintBluetoothThermal.connectionStatus; if (!isConnected) { log("Not connected to Bluetooth printer", name: _logName); return false; } bool printResult = await PrintBluetoothThermal.writeBytes(printData); if (printResult) { log("Successfully printed via Bluetooth", name: _logName); } else { FirebaseCrashlytics.instance.recordError( 'Failed to print via Bluetooth', null, reason: 'Failed to print via Bluetooth', information: [ 'function: printBluetooth(List printData)', 'printData: $printData', 'in: $_logName', ], ); log("Failed to print via Bluetooth", name: _logName); } return printResult; } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( e, stackTrace, reason: 'Error printing via Bluetooth', information: [ 'function: printBluetooth(List printData)', 'Printer: Bluetooth printer', 'printData: $printData', 'in: $_logName', ], ); log("Error printing via Bluetooth: $e", name: _logName); return false; } } Future _printNetwork(String ipAddress, List printData) async { try { final printer = PrinterNetworkManager(ipAddress); PosPrintResult connect = await printer.connect(); if (connect == PosPrintResult.success) { PosPrintResult printing = await printer.printTicket(printData); printer.disconnect(); if (printing == PosPrintResult.success) { log("Successfully printed via Network printer: $ipAddress"); return true; } else { FirebaseCrashlytics.instance.recordError( 'Failed to print via Network printer: ${printing.msg}', null, reason: 'Failed to print via Network printer', information: [ 'function: printNetwork(String ipAddress, List printData)', 'Printer: Network printer', 'ipAddress: $ipAddress', 'printData: $printData', ], ); log("Failed to print via Network printer: ${printing.msg}"); return false; } } else { FirebaseCrashlytics.instance.recordError( 'Failed to connect to Network printer: ${connect.msg}', null, reason: 'Failed to connectNetwork printer', information: [ 'function: printNetwork(String ipAddress, List printData)', 'Printer: Network printer', 'ipAddress: $ipAddress', 'printData: $printData', ], ); log("Failed to connect to Network printer: ${connect.msg}"); return false; } } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( e, stackTrace, reason: 'Error printing via Network', information: [ 'function: printNetwork(String ipAddress, List printData)', 'Printer: Network printer', 'ipAddress: $ipAddress', 'printData: $printData', ], ); log("Error printing via Network: $e"); return false; } } @override Future> printStruckOrder({ required Order order, }) async { try { final outlet = await _outletLocalDatasource.currentOutlet(); final user = await _authLocalDataProvider.currentUser(); // Struck for customer if (order.totalPaid > 0) { _printReceipt(order: order, outlet: outlet, cashieName: user.name); } // Struck for cashier _printCashier(order: order, outlet: outlet, cashieName: user.name); // Struck for kitchen _printKitchen(order: order, outlet: outlet, cashieName: user.name); // Struck for checker _printChecker(order: order, outlet: outlet, cashieName: user.name); // Struck for bar if exist product bar final itemsBar = order.orderItems.where( (item) => item.printerType == 'bar', ); if (itemsBar.isNotEmpty) { _printBar(order: order, outlet: outlet, cashieName: user.name); } return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Order Error: $e', stackTrace, reason: 'Print struck order error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing struck order", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage('Error printing order'), ); } } @override Future> printStruckSaveOrder({ required Order order, }) async { try { final outlet = await _outletLocalDatasource.currentOutlet(); final user = await _authLocalDataProvider.currentUser(); // Struck for cashier _printCashier(order: order, outlet: outlet, cashieName: user.name); // Struck for kitchen _printKitchen(order: order, outlet: outlet, cashieName: user.name); // Struck for checker _printChecker(order: order, outlet: outlet, cashieName: user.name); // Struck for bar if exist product bar final itemsBar = order.orderItems.where( (item) => item.printerType == 'bar', ); if (itemsBar.isNotEmpty) { _printBar(order: order, outlet: outlet, cashieName: user.name); } return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Save Order Error: $e', stackTrace, reason: 'Print struck save order error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing struck save order", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage('Error printing save order'), ); } } @override Future> printStruckReceipt({ required Order order, }) async { final outlet = await _outletLocalDatasource.currentOutlet(); final user = await _authLocalDataProvider.currentUser(); return _printCashier(order: order, outlet: outlet, cashieName: user.name); } Future> _printReceipt({ required Order order, required Outlet outlet, required String cashieName, }) async { log('Starting to print receipt', name: _logName); final receiptPrinter = await _localDataProvider.findPrinterByCode( 'receipt', ); if (receiptPrinter.hasData) { try { final printer = receiptPrinter.data!.toDomain(); final printValue = await PrintUi().printOrder( order: order, outlet: outlet, cashierName: cashieName, ); await printStruct(printer, printValue); log('Finished printed receipt', name: _logName); return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Receipt Error: $e', stackTrace, reason: 'Print struck receipt error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing receipt", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage( 'Error printing receipt order', ), ); } } else { FirebaseCrashlytics.instance.recordError( 'Receipt printer not found', null, reason: 'Receipt printer not found / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); return left( const PrinterFailure.dynamicErrorMessage('Receipt printer not found'), ); } } Future> _printCashier({ required Order order, required Outlet outlet, required String cashieName, }) async { log('Starting to print cashier', name: _logName); final receiptPrinter = await _localDataProvider.findPrinterByCode( 'receipt', ); if (receiptPrinter.hasData) { try { final printer = receiptPrinter.data!.toDomain(); final printValue = await PrintUi().printCashier( order: order, outlet: outlet, cashierName: cashieName, ); await printStruct(printer, printValue); log('Finished printing cashier', name: _logName); return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Cashier Error: $e', stackTrace, reason: 'Print struck cashier error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing cashier", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage( 'Error printing cashier order', ), ); } } else { FirebaseCrashlytics.instance.recordError( 'Cashier printer not found', null, reason: 'Cashier printer not found / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); return left( const PrinterFailure.dynamicErrorMessage('Cashier printer not found'), ); } } Future> _printKitchen({ required Order order, required Outlet outlet, required String cashieName, }) async { log('Starting to print kitchen', name: _logName); final kitchenPrinter = await _localDataProvider.findPrinterByCode( 'kitchen', ); if (kitchenPrinter.hasData) { try { final printer = kitchenPrinter.data!.toDomain(); final printValue = await PrintUi().printKitchen( order: order, outlet: outlet, cashierName: cashieName, ); await printStruct(printer, printValue); log('Finished printed kitchen', name: _logName); return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Kitchen Error: $e', stackTrace, reason: 'Print struck kitchen error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing kitchen", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage( 'Error printing kitchen order', ), ); } } else { FirebaseCrashlytics.instance.recordError( 'Kitchen printer not found', null, reason: 'Kitchen printer not found / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); return left( const PrinterFailure.dynamicErrorMessage('Kitchen printer not found'), ); } } Future> _printChecker({ required Order order, required Outlet outlet, required String cashieName, }) async { log('Starting to print checker', name: _logName); final checkerPrinter = await _localDataProvider.findPrinterByCode( 'checker', ); if (checkerPrinter.hasData) { try { final printer = checkerPrinter.data!.toDomain(); final printValue = await PrintUi().printChecker( order: order, outlet: outlet, cashierName: cashieName, ); await printStruct(printer, printValue); log('Finished printed checker', name: _logName); return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Checker Error: $e', stackTrace, reason: 'Print struck checker error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing checker", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage( 'Error printing checker order', ), ); } } else { FirebaseCrashlytics.instance.recordError( 'Checker printer not found', null, reason: 'Checker printer not found / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); return left( const PrinterFailure.dynamicErrorMessage('Checker printer not found'), ); } } Future> _printBar({ required Order order, required Outlet outlet, required String cashieName, }) async { final barPrinter = await _localDataProvider.findPrinterByCode('bar'); if (barPrinter.hasData) { log('Starting to print bar', name: _logName); try { final productBar = order.orderItems .where((item) => item.printerType == 'bar') .toList(); if (productBar.isNotEmpty) { final printer = barPrinter.data!.toDomain(); final printValue = await PrintUi().printBar( order: order, outlet: outlet, cashierName: cashieName, ); await printStruct(printer, printValue); log('Finished printed bar', name: _logName); } else { log('Product with printer type bar not found', name: _logName); return right(unit); } return right(unit); } catch (e, stackTrace) { FirebaseCrashlytics.instance.recordError( 'Print Struck Bar Error: $e', stackTrace, reason: 'Print struck bar error / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); log("Error printing bar", name: _logName, error: e); return left( const PrinterFailure.dynamicErrorMessage('Error printing bar order'), ); } } else { FirebaseCrashlytics.instance.recordError( 'Bar printer not found', null, reason: 'Bar printer not found / Printer not setting in printer page', information: ['Order ID: ${order.id}'], ); return left( const PrinterFailure.dynamicErrorMessage('Bar printer not found'), ); } } }