import 'dart:async'; import 'package:flutter/material.dart'; import 'package:bluetooth_print/bluetooth_print_model.dart'; import '../services/bluetooth_service.dart'; class BluetoothScanDialog extends StatefulWidget { final BluetoothService bluetoothService; final void Function(BluetoothDevice device, bool success)? onDeviceConnected; const BluetoothScanDialog({ super.key, required this.bluetoothService, this.onDeviceConnected, }); @override State createState() => _BluetoothScanDialogState(); } class _BluetoothScanDialogState extends State { bool _isScanning = false; List _scannedDevices = []; String? _connectingAddress; StreamSubscription>? _scanResultsSub; StreamSubscription? _isScanningSub; @override void initState() { super.initState(); // 1. Langsung berlangganan ke stream hasil scan agar perangkat yang ditemukan seketika tampil di UI _scanResultsSub = widget.bluetoothService.scanResults.listen((devices) { if (mounted) { setState(() { _scannedDevices = devices; }); } }); // 2. Berlangganan ke stream status scan untuk sinkronisasi indikator loading _isScanningSub = widget.bluetoothService.isScanning.listen((scanning) { if (mounted) { setState(() { _isScanning = scanning; }); } }); // 3. Mulai pemindaian perdana tepat 1 kali saat dialog dibuka WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _startScan(); } }); } @override void dispose() { _scanResultsSub?.cancel(); _isScanningSub?.cancel(); widget.bluetoothService.stopScan(); super.dispose(); } Future _startScan() async { if (_connectingAddress != null) return; if (mounted) { setState(() { _isScanning = true; _scannedDevices.clear(); }); } try { await widget.bluetoothService.startScan(timeout: const Duration(seconds: 5)); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal memindai: $e')), ); } } } Future _connect(BluetoothDevice device) async { if (_connectingAddress != null) return; setState(() { _connectingAddress = device.address; }); // Hentikan pemindaian terlebih dahulu sebelum menyambungkan ke BLE device await widget.bluetoothService.stopScan(); final ok = await widget.bluetoothService.connectToDevice(device); if (mounted) { widget.onDeviceConnected?.call(device, ok); Navigator.of(context).pop(); } } @override Widget build(BuildContext context) { return PopScope( canPop: true, onPopInvokedWithResult: (didPop, _) { if (didPop) { widget.bluetoothService.stopScan(); } }, child: AlertDialog( title: Row( children: [ const Icon(Icons.bluetooth_searching, color: Colors.blue), const SizedBox(width: 8), const Expanded( child: Text( 'Pindai Printer Bluetooth', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), if (_isScanning) const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ), ], ), content: SizedBox( width: MediaQuery.of(context).size.width, height: 320, child: Column( children: [ if (_isScanning) const Padding( padding: EdgeInsets.symmetric(vertical: 6.0), child: Text( 'Mencari printer thermal di sekitar...', style: TextStyle(color: Colors.grey, fontSize: 12), ), ), Expanded( child: _scannedDevices.isEmpty ? Center( child: _isScanning ? const Text('Sedang memindai perangkat...') : const Text( 'Tidak ada perangkat Bluetooth ditemukan.\nPastikan Bluetooth & printer aktif.', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), ) : ListView.builder( itemCount: _scannedDevices.length, itemBuilder: (context, index) { final dev = _scannedDevices[index]; final isConnectingThis = _connectingAddress == dev.address; return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( leading: const Icon(Icons.print, color: Colors.blue), title: Text( dev.name ?? 'Perangkat Bluetooth', style: const TextStyle(fontWeight: FontWeight.w600), ), subtitle: Text(dev.address ?? ''), trailing: ElevatedButton( onPressed: (_connectingAddress != null) ? null : () => _connect(dev), child: isConnectingThis ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Hubungkan'), ), ), ); }, ), ), ], ), ), actions: [ TextButton( onPressed: (_isScanning || _connectingAddress != null) ? null : () => _startScan(), child: const Text('Pindai Ulang'), ), TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Tutup'), ), ], ), ); } }