import 'package:flutter/material.dart'; import 'package:cashumit/models/receipt_item.dart'; import 'package:cashumit/screens/add_item_screen.dart'; import 'package:bluetooth_print/bluetooth_print.dart'; import 'package:cashumit/services/bluetooth_service.dart'; import 'package:cashumit/widgets/receipt_body.dart'; // Import widget komponen struk baru import 'package:cashumit/widgets/receipt_tear_effect.dart'; import 'package:cashumit/widgets/store_info_config_dialog.dart'; import 'package:cashumit/widgets/custom_text_config_dialog.dart'; import 'package:cashumit/screens/webview_screen.dart'; import 'package:cashumit/widgets/receipt_speed_dial.dart'; import 'package:cashumit/widgets/printing_status_card.dart'; // Import service baru import 'package:cashumit/services/account_dialog_service.dart'; import 'package:cashumit/services/esc_pos_print_service.dart'; // Import provider import 'package:provider/provider.dart'; import 'package:cashumit/providers/receipt_provider.dart'; // Import untuk penanganan error import 'dart:io'; import 'package:flutter/services.dart'; class ReceiptScreen extends StatefulWidget { const ReceiptScreen({super.key}); @override State createState() => _ReceiptScreenState(); } class _ReceiptScreenState extends State { // Bluetooth service final BluetoothService _bluetoothService = BluetoothService(); // Printing status bool _isPrinting = false; @override void initState() { super.initState(); print('Inisialisasi ReceiptScreen...'); _initBluetooth(); _loadSavedBluetoothDevice(); print('Selesai inisialisasi ReceiptScreen'); // Panggil inisialisasi provider setelah widget dibuat WidgetsBinding.instance.addPostFrameCallback((_) { final receiptProvider = context.read(); receiptProvider.initialize(); }); } @override void didChangeDependencies() { super.didChangeDependencies(); } @override void dispose() { super.dispose(); } /// Memuat device bluetooth yang tersimpan Future _loadSavedBluetoothDevice() async { await _bluetoothService.loadSavedDevice(); } /// Memeriksa status koneksi Bluetooth printer secara real-time Future _checkBluetoothConnection() async { return await _bluetoothService.checkConnection(); } /// Inisialisasi Bluetooth printer Future _initBluetooth() async { // Inisialisasi BluetoothService await _bluetoothService.initialize(); // Listen to bluetooth state changes _bluetoothService.state.listen((state) { if (mounted) { switch (state) { case BluetoothPrint.connected: setState(() { // Status koneksi akan dikelola oleh BluetoothService }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Printer terhubung')), ); } break; case BluetoothPrint.disconnected: setState(() { // Status koneksi akan dikelola oleh BluetoothService }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Printer terputus')), ); } break; default: break; } } }); } /// Cetak struk ke printer thermal Future _printToThermalPrinter() async { final receiptProvider = context.read(); final state = receiptProvider.state; try { // Cek dan reconnect jika perlu final isConnected = await _bluetoothService.reconnectIfNeeded(); if (!isConnected) { if (!mounted) return; if (_bluetoothService.connectedDevice != null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Gagal menyambungkan ke printer')), ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Harap hubungkan printer terlebih dahulu')), ); } return; } await EscPosPrintService.printToThermalPrinter( items: state.items, transactionDate: state.transactionDate, context: context, bluetoothService: _bluetoothService, ); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Perintah cetak dikirim ke printer')), ); } on SocketException catch (e) { // Tangani error koneksi secara spesifik if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Koneksi ke printer terputus: ${e.message}')), ); } on PlatformException catch (e) { // Tangani error platform secara spesifik if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error printer: ${e.message}')), ); } catch (e) { // Tangani error umum if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal mencetak struk: $e')), ); } } /// Methods to control printing status void _startPrinting() { setState(() { _isPrinting = true; }); } void _endPrinting() { setState(() { _isPrinting = false; }); } void _addItem() async { final newItem = await showDialog( context: context, builder: (context) => const AddItemScreen(), // Tampilkan sebagai dialog ); if (newItem != null) { // Menambahkan item melalui provider final receiptProvider = context.read(); receiptProvider.addItem(newItem); } } void _editItem(int index) async { final receiptProvider = context.read(); final state = receiptProvider.state; // Pastikan index valid sebelum mengakses item if (index < 0 || index >= state.items.length) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Gagal mengedit item: Index tidak valid.")), ); } return; } final originalItem = state.items[index]; // Simpan item asli untuk fallback jika diperlukan final editedItem = await showDialog( context: context, builder: (context) => AddItemScreen.fromItem(originalItem), // Tampilkan sebagai dialog ); // Hanya update jika item tidak null (bukan hasil dari 'Batal') if (editedItem != null) { // Memperbarui item melalui provider receiptProvider.editItem(index, editedItem); } // Jika editedItem null (dibatalkan), tidak ada perubahan pada items[index] } /// Menampilkan dialog untuk memilih akun sumber (revenue) Future _selectSourceAccount() async { final receiptProvider = context.read(); final state = receiptProvider.state; final selectedAccount = await AccountDialogService.showSourceAccountDialog( context, state.accounts ); if (selectedAccount != null) { // Memperbarui state melalui provider receiptProvider.selectSourceAccount( selectedAccount['id'].toString(), selectedAccount['name'].toString() ); } } /// Menampilkan dialog untuk memilih akun tujuan (asset) Future _selectDestinationAccount() async { final receiptProvider = context.read(); final state = receiptProvider.state; final selectedAccount = await AccountDialogService.showDestinationAccountDialog( context, state.accounts ); if (selectedAccount != null) { // Memperbarui state melalui provider receiptProvider.selectDestinationAccount( selectedAccount['id'].toString(), selectedAccount['name'].toString() ); } } /// Mengirim transaksi ke Firefly III. Future _sendToFirefly() async { final receiptProvider = context.read(); final state = receiptProvider.state; try { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Mengirim transaksi ke Firefly III...')), ); final transactionId = await receiptProvider.submitTransaction(); if (!mounted) return; if (transactionId != null && transactionId != "success") { // Navigasi ke WebViewScreen untuk menampilkan transaksi if (state.fireflyUrl != null) { final transactionUrl = '${state.fireflyUrl}/transactions/show/$transactionId'; if (mounted) { Navigator.push( context, MaterialPageRoute( builder: (context) => WebViewScreen( url: transactionUrl, title: 'Transaksi Firefly III', ), ), ); } } } else if (transactionId == "success") { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Transaksi berhasil dikirim ke Firefly III (tanpa ID)')), ); } else { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Gagal mengirim transaksi ke Firefly III. Periksa log untuk detail kesalahan.')), ); } } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal mengirim transaksi: $e')), ); } } /// Membuka dialog konfigurasi informasi toko Future _openStoreInfoConfig() async { final result = await showDialog( context: context, builder: (context) => const StoreInfoConfigDialog(), ); // Jika informasi toko berhasil disimpan, refresh widget if (result == true) { setState(() { // Trigger rebuild untuk memuat ulang data }); } } /// Membuka dialog konfigurasi teks kustom (disclaimer, thank you, pantun) Future _openCustomTextConfig() async { final result = await showDialog( context: context, builder: (context) => const CustomTextConfigDialog(), ); // Jika teks berhasil disimpan, refresh widget if (result == true) { setState(() { // Trigger rebuild untuk memuat ulang data }); } } /// Membuka layar konfigurasi. void _openSettings() async { final result = await Navigator.pushNamed(context, '/config'); // Jika pengguna kembali dari ConfigScreen dengan hasil true, muat ulang kredensial dan akun if (result == true) { final receiptProvider = context.read(); receiptProvider.loadCredentialsAndAccounts(); } } final TextStyle baseTextStyle = const TextStyle( fontFamily: 'Courier', // Gunakan font courier jika tersedia fontSize: 14, height: 1.2, ); @override Widget build(BuildContext context) { return Stack( children: [ Scaffold( backgroundColor: Colors.grey[300], // Latar belakang abu-abu untuk efek struk floatingActionButton: Consumer( builder: (context, receiptProvider, child) { final state = receiptProvider.state; return ReceiptSpeedDial( bluetoothService: _bluetoothService, onCheckConnection: _checkBluetoothConnection, onPrint: _printToThermalPrinter, onSettings: _openSettings, onReloadAccounts: receiptProvider.loadAccounts, hasItems: state.items.isNotEmpty, hasSourceAccount: state.sourceAccountId != null, hasDestinationAccount: state.destinationAccountId != null, onSendToFirefly: _sendToFirefly, onPrintingStart: _startPrinting, onPrintingEnd: _endPrinting, ); } ), body: SafeArea( child: Center( // Membungkus dengan widget Center untuk memastikan struk berada di tengah child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment .center, // Memusatkan konten secara horizontal children: [ // Background untuk efek kertas struk tersobek di bagian atas Container( width: 360, color: const Color(0xFFE0E0E0), // Warna latar belakang sesuai dengan latar belakang layar child: const Column( children: [ SizedBox(height: 15), // Jarak atas yang lebih besar ReceiptTearTop(), // Efek kertas struk tersobek di bagian atas ], ), ), // Konten struk Consumer( builder: (context, receiptProvider, child) { final state = receiptProvider.state; return ReceiptBody( items: state.items, sourceAccountName: state.sourceAccountName, destinationAccountName: state.destinationAccountName, onOpenStoreInfoConfig: _openStoreInfoConfig, onSelectSourceAccount: () => _selectSourceAccount(), // Memanggil fungsi langsung onSelectDestinationAccount: () => _selectDestinationAccount(), // Memanggil fungsi langsung onOpenCustomTextConfig: _openCustomTextConfig, total: state.total, onEditItem: (index) => _editItem(index), // Memanggil fungsi langsung onRemoveItem: (index) { // Validasi index untuk mencegah error if (index >= 0 && index < state.items.length) { // Hapus item dari daftar melalui provider receiptProvider.removeItem(index); } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( "Gagal menghapus item: Index tidak valid.")), ); } } }, onAddItem: () => _addItem(), // Memanggil fungsi langsung ); } ), // Background untuk efek kertas struk tersobek di bagian bawah Container( width: 360, color: const Color(0xFFE0E0E0), // Warna latar belakang sesuai dengan latar belakang layar child: const Column( children: [ ReceiptTearBottom(), // Efek kertas struk tersobek di bagian bawah SizedBox(height: 15), // Jarak bawah yang lebih besar ], ), ), ], ), ), ), ), ), PrintingStatusCard( isVisible: _isPrinting, onDismiss: () { setState(() { _isPrinting = false; }); }, ), ], ); } }