import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../database/siab_database.dart'; import '../models/registered_device.dart'; import '../services/ble_service.dart'; import '../siab_config.dart'; class DevicesScreen extends StatefulWidget { const DevicesScreen({super.key}); @override State createState() => _DevicesScreenState(); } class _DevicesScreenState extends State { final _db = SiabDatabase(); List _saved = []; List _scanResults = []; bool _loading = false; Future _loadSaved() async { setState(() => _loading = true); _saved = await _db.getAll(); setState(() => _loading = false); } Future _scan() async { final ble = context.read(); setState(() => _scanResults = []); final list = await ble.scanForAllSiabDevices(); setState(() => _scanResults = list); } Future _register( SiabScanResult result, String label) async { await _db.insert(RegisteredDevice( uuid: result.address, label: label.isEmpty ? result.address : label, )); await _loadSaved(); } Future _connectToScanResult(SiabScanResult result) async { final ble = context.read(); final ok = await ble.connectToDevice(result.device); if (mounted && !ok) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Connection failed'), backgroundColor: Colors.red), ); } else if (mounted && ok) { await Future.delayed(const Duration(seconds: 2)); if (mounted && ble.packetCount == 0) ble.tryStartCommands(); await _loadSaved(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Connected'), backgroundColor: Colors.green), ); } } } Future _connectToSaved(RegisteredDevice device) async { final ble = context.read(); final ok = await ble.connectToAddress(device.uuid); if (mounted && !ok) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Device not found or connection failed'), backgroundColor: Colors.red), ); } else if (mounted && ok) { await Future.delayed(const Duration(seconds: 2)); if (mounted && ble.packetCount == 0) ble.tryStartCommands(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Connected'), backgroundColor: Colors.green), ); } } } Future _unregisterDevice(RegisteredDevice device) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Unregister device'), content: Text( 'Remove \"${device.label}\" (${device.uuid}) from registered devices?', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel'), ), FilledButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('Unregister'), ), ], ), ); if (confirmed != true) return; if (device.id != null) { await _db.delete(device.id!); } else { await _db.deleteByUuid(device.uuid); } await _loadSaved(); } @override void initState() { super.initState(); _loadSaved(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Timbangan'), actions: [ IconButton( onPressed: _loading ? null : _loadSaved, icon: const Icon(Icons.refresh), ), ], ), body: Consumer( builder: (context, ble, _) { return ListView( padding: const EdgeInsets.all(16), children: [ // Scan section at top const Text('Daftar Timbangan (scan)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 8), OutlinedButton.icon( onPressed: ble.isScanning ? null : () async { await _scan(); }, icon: ble.isScanning ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.bluetooth_searching, size: 20), label: Text(ble.isScanning ? 'Scanning...' : 'Scan Timbangan'), ), const SizedBox(height: 12), if (_scanResults.isEmpty && !ble.isScanning) const Card(child: Padding( padding: EdgeInsets.all(16), child: Text('Tap \"Scan Timbangan\" lihat melihat timbangan di sekitar.'), )) else ..._scanResults.map((r) { RegisteredDevice? registered; for (final d in _saved) { if (d.uuid == r.address) { registered = d; break; } } final labelId = registered?.label; final hasLabel = labelId != null && labelId.isNotEmpty; return Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _ListRow(label: 'NAMA', value: labelId ?? '—'), _ListRow(label: 'ID', value: r.device.remoteId.toString()), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ if (!hasLabel) Padding( padding: const EdgeInsets.only(right: 8), child: TextButton.icon( onPressed: () => _showRegisterDialog(r), icon: const Icon(Icons.app_registration, size: 18), label: const Text('Register'), ), ), TextButton.icon( onPressed: ble.isConnecting ? null : () => _connectToScanResult(r), icon: const Icon(Icons.link, size: 18), label: const Text('Connect'), ), ], ), ], ), ), ); }), const SizedBox(height: 24), // Registered devices below scan const Text('Registered devices (saved)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 8), if (_loading) const Center(child: CircularProgressIndicator()) else if (_saved.isEmpty) const Card(child: Padding( padding: EdgeInsets.all(16), child: Text('No registered devices. Scan and register above.'), )) else ..._saved.map((d) => Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _ListRow(label: 'NAMA', value: d.label), _ListRow(label: 'ID', value: d.uuid), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( onPressed: () => _unregisterDevice(d), icon: const Icon(Icons.delete_outline, size: 18), label: const Text('Unregister'), ), const SizedBox(width: 8), if (ble.isConnected && ble.deviceAddress == d.uuid) const Icon(Icons.bluetooth_connected, color: Colors.green, size: 28) else TextButton.icon( onPressed: ble.isConnecting ? null : () => _connectToSaved(d), icon: const Icon(Icons.link, size: 18), label: const Text('Connect'), ), ], ), ], ), ), )), ], ); }, ), ); } void _showRegisterDialog(SiabScanResult result) { final controller = TextEditingController( text: result.name.isNotEmpty ? result.name : SiabConfig.deviceName, ); showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Register device'), content: TextField( controller: controller, decoration: const InputDecoration( labelText: 'Nama', hintText: 'contoh : Scale 1, TIMBANGAN 1', ), autofocus: true, ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Cancel'), ), FilledButton( onPressed: () async { final label = controller.text.trim(); await _register(result, label.isEmpty ? result.address : label); if (mounted) Navigator.pop(ctx); }, child: const Text('Save'), ), ], ), ); } } class _ListRow extends StatelessWidget { final String label; final String value; const _ListRow({required this.label, required this.value}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 110, child: Text( '$label:', style: TextStyle( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8), fontSize: 13, ), ), ), Expanded( child: SelectableText( value, style: const TextStyle(fontSize: 13), ), ), ], ), ); } }