import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'dart:async'; import 'constants/app_colors.dart'; class BluetoothScreen extends StatefulWidget { const BluetoothScreen({super.key}); @override State createState() => _BluetoothScreenState(); } class _BluetoothScreenState extends State { List _scanResults = []; bool _isScanning = false; StreamSubscription? _scanResultsSubscription; StreamSubscription? _isScanningSubscription; @override void initState() { super.initState(); // Ouvir os resultados da busca e ordenar _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) { if (mounted) { // Criar uma cópia da lista e ordenar List sortedResults = List.from(results); sortedResults.sort((a, b) { bool aHasName = _hasRealName(a); bool bHasName = _hasRealName(b); if (aHasName && !bHasName) return -1; // 'a' vem primeiro if (!aHasName && bHasName) return 1; // 'b' vem primeiro return 0; // Mantém a ordem original entre iguais }); setState(() { _scanResults = sortedResults; }); } }); // Ouvir o estado da busca _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) { if (mounted) { setState(() { _isScanning = state; }); } }); } // Função auxiliar para verificar se tem nome real bool _hasRealName(ScanResult r) { return r.advertisementData.advName.isNotEmpty || r.device.platformName.isNotEmpty; } @override void dispose() { _scanResultsSubscription?.cancel(); _isScanningSubscription?.cancel(); super.dispose(); } Future startScan() async { Map statuses = await [ Permission.bluetoothScan, Permission.bluetoothConnect, Permission.location, ].request(); if (statuses[Permission.bluetoothScan]!.isGranted && statuses[Permission.bluetoothConnect]!.isGranted) { try { _scanResults.clear(); await FlutterBluePlus.startScan( timeout: const Duration(seconds: 15), androidUsesFineLocation: true, continuousUpdates: true, ); Future.delayed(const Duration(seconds: 8), () { if (mounted && _scanResults.isEmpty && _isScanning) { _showSnackBar("Nenhum dispositivo encontrado.", AppColors.coral); } }); } catch (e) { _showSnackBar("Erro ao iniciar busca: $e", Colors.red); } } else { _showSnackBar("Permissões negadas.", AppColors.coral); } } Future stopScan() async { try { await FlutterBluePlus.stopScan(); } catch (e) { _showSnackBar("Erro ao parar busca: $e", Colors.red); } } Future connectToDevice(BluetoothDevice device) async { try { String name = device.platformName.isNotEmpty ? device.platformName : "Dispositivo"; _showSnackBar("Conectando a $name...", Colors.white70); await device.connect(); _showSnackBar("Conectado com sucesso!", Colors.green); } catch (e) { _showSnackBar("Falha ao conectar: $e", Colors.red); } } // Lógica para capturar o nome String _getDeviceName(ScanResult r) { if (r.advertisementData.advName.isNotEmpty) { return r.advertisementData.advName; } else if (r.device.platformName.isNotEmpty) { return r.device.platformName; } else { String id = r.device.remoteId.toString(); return "Disp. [${id.length > 5 ? id.substring(id.length - 5) : id}]"; } } void _showSnackBar(String message, Color color) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: color, duration: const Duration(seconds: 3), behavior: SnackBarBehavior.floating, ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Bluetooth', style: TextStyle(fontWeight: FontWeight.bold)), centerTitle: true, backgroundColor: Colors.transparent, elevation: 0, foregroundColor: AppColors.white, ), extendBodyBehindAppBar: true, backgroundColor: AppColors.background, body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.coral.withOpacity(0.1), AppColors.background, ], ), ), child: Column( children: [ const SizedBox(height: 100), Center( child: Column( children: [ Container( padding: const EdgeInsets.all(25), decoration: BoxDecoration( shape: BoxShape.circle, color: _isScanning ? AppColors.coral.withOpacity(0.1) : AppColors.backgroundGrey.withOpacity(0.3), border: Border.all( color: _isScanning ? AppColors.coral : AppColors.backgroundGrey, width: 2, ), ), child: Icon( _isScanning ? Icons.bluetooth_searching : Icons.bluetooth, size: 60, color: _isScanning ? AppColors.coral : AppColors.white.withOpacity(0.7), ), ), const SizedBox(height: 30), SizedBox( width: 220, height: 50, child: ElevatedButton( onPressed: _isScanning ? stopScan : startScan, style: ElevatedButton.styleFrom( backgroundColor: _isScanning ? AppColors.backgroundGrey : AppColors.white, foregroundColor: _isScanning ? AppColors.white : AppColors.background, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), elevation: 0, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_isScanning) const Padding( padding: EdgeInsets.only(right: 10), child: SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: AppColors.white, ), ), ), Text( _isScanning ? 'PARAR BUSCA' : 'BUSCAR AGORA', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), ], ), ), ), ], ), ), const SizedBox(height: 40), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( children: [ Text( _isScanning ? "Procurando..." : "Dispositivos próximos", style: const TextStyle( color: AppColors.white, fontSize: 14, fontWeight: FontWeight.w500, ), ), const Spacer(), if (_isScanning) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: AppColors.coral.withOpacity(0.2), borderRadius: BorderRadius.circular(10), ), child: const Text( "ATIVO", style: TextStyle(color: AppColors.coral, fontSize: 10, fontWeight: FontWeight.bold), ), ), ], ), ), const SizedBox(height: 15), Expanded( child: _scanResults.isEmpty && !_isScanning ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.bluetooth_disabled, size: 40, color: AppColors.white.withOpacity(0.1)), const SizedBox(height: 16), Text( "Inicie a busca para conectar", style: TextStyle(color: AppColors.white.withOpacity(0.3)), ), ], ), ) : ListView.builder( padding: const EdgeInsets.only(top: 0, bottom: 20), itemCount: _scanResults.length, itemBuilder: (context, index) { final r = _scanResults[index]; final name = _getDeviceName(r); final isUnknown = name.startsWith("Disp. ["); return Container( margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 6), decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity(0.2), borderRadius: BorderRadius.circular(15), ), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), leading: CircleAvatar( backgroundColor: AppColors.backgroundGrey.withOpacity(0.3), child: Icon( isUnknown ? Icons.devices_other : Icons.bluetooth, color: isUnknown ? AppColors.white.withOpacity(0.4) : AppColors.white, size: 20 ), ), title: Text( name, style: TextStyle( color: isUnknown ? AppColors.white.withOpacity(0.5) : AppColors.white, fontWeight: FontWeight.w600, fontSize: 15, ), ), subtitle: Text( r.device.remoteId.toString(), style: TextStyle(color: AppColors.white.withOpacity(0.3), fontSize: 11), ), trailing: ElevatedButton( onPressed: () => connectToDevice(r.device), style: ElevatedButton.styleFrom( backgroundColor: AppColors.coral, foregroundColor: AppColors.white, padding: const EdgeInsets.symmetric(horizontal: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), elevation: 0, ), child: const Text("CONECTAR", style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold)), ), ), ); }, ), ), ], ), ), ); } }