diff --git a/lib/bluetooth_connection_screen.dart b/lib/bluetooth_connection_screen.dart new file mode 100644 index 0000000..193242f --- /dev/null +++ b/lib/bluetooth_connection_screen.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'dart:async'; + +class BluetoothConnectionScreen extends StatefulWidget { + const BluetoothConnectionScreen({super.key}); + + @override + State createState() => _BluetoothConnectionScreenState(); +} + +class _BluetoothConnectionScreenState extends State { + List scanResults = []; + bool isScanning = false; + StreamSubscription? scanSubscription; + StreamSubscription? isScanningSubscription; + + @override + void initState() { + super.initState(); + // Monitorar se o sistema está escaneando + isScanningSubscription = FlutterBluePlus.isScanning.listen((scanning) { + if (mounted) { + setState(() { + isScanning = scanning; + }); + } + }); + + // Ouvir os resultados do scan globalmente + scanSubscription = FlutterBluePlus.scanResults.listen((results) { + if (mounted) { + setState(() { + scanResults = results; + }); + } + }); + } + + @override + void dispose() { + scanSubscription?.cancel(); + isScanningSubscription?.cancel(); + super.dispose(); + } + + Future startScan() async { + // 1. Pedir permissões (necessário no Android) + Map statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, + ].request(); + + if (statuses[Permission.bluetoothScan]!.isGranted && + statuses[Permission.bluetoothConnect]!.isGranted && + statuses[Permission.location]!.isGranted) { + + setState(() { + scanResults.clear(); + }); + + // 2. Iniciar o scan + try { + await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erro ao buscar: $e')), + ); + } + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Permissões negadas')), + ); + } + } + } + + Future connectToDevice(BluetoothDevice device) async { + try { + await device.connect(); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Conectado a ${device.platformName.isEmpty ? 'Dispositivo' : device.platformName}')), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Falha ao conectar: $e')), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Conexão Bluetooth'), + backgroundColor: Colors.grey[850], + foregroundColor: Colors.white, + ), + backgroundColor: Colors.grey[900], + body: Column( + children: [ + const SizedBox(height: 20), + Center( + child: Column( + children: [ + const Icon( + Icons.bluetooth, + size: 80, + color: Colors.white, + ), + const SizedBox(height: 10), + ElevatedButton.icon( + onPressed: isScanning ? null : startScan, + icon: isScanning + ? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black)) + : const Icon(Icons.search), + label: Text(isScanning ? 'Buscando...' : 'Procurar Dispositivos'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), + textStyle: const TextStyle(fontSize: 18), + ), + ), + ], + ), + ), + const SizedBox(height: 20), + Expanded( + child: scanResults.isEmpty && !isScanning + ? const Center( + child: Text( + "Nenhum dispositivo encontrado.\nClique em procurar.", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white70), + ), + ) + : ListView.builder( + itemCount: scanResults.length, + itemBuilder: (context, index) { + final device = scanResults[index].device; + final name = device.platformName.isEmpty ? 'Desconhecido' : device.platformName; + return Card( + color: Colors.white10, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: ListTile( + title: Text(name, style: const TextStyle(color: Colors.white)), + subtitle: Text(device.remoteId.toString(), style: const TextStyle(color: Colors.white70)), + trailing: const Icon(Icons.link, color: Colors.blue), + onTap: () => connectToDevice(device), + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/bluetooth_screen.dart b/lib/bluetooth_screen.dart new file mode 100644 index 0000000..a5719a8 --- /dev/null +++ b/lib/bluetooth_screen.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'dart:async'; + +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(); + + _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) { + if (mounted) { + setState(() { + _scanResults = results; + }); + } + }); + + _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) { + if (mounted) { + setState(() { + _isScanning = state; + }); + } + }); + } + + @override + void dispose() { + _scanResultsSubscription?.cancel(); + _isScanningSubscription?.cancel(); + super.dispose(); + } + + Future startScan() async { + // 1. Pedir permissões + Map statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, + ].request(); + + if (statuses[Permission.bluetoothScan]!.isGranted && + statuses[Permission.bluetoothConnect]!.isGranted) { + + try { + // 2. Iniciar Scan + await FlutterBluePlus.startScan( + timeout: const Duration(seconds: 15), + ); + + // Se não encontrar nada (ex: no emulador), avisa o usuário + Future.delayed(const Duration(seconds: 5), () { + if (mounted && _scanResults.isEmpty && _isScanning) { + _showSnackBar("Nenhum dispositivo encontrado (Verifique se o Bluetooth/GPS estão ligados)", Colors.orange); + } + }); + + } catch (e) { + _showSnackBar("Erro ao iniciar busca: $e", Colors.red); + } + } else { + _showSnackBar("Permissões de Bluetooth negadas", Colors.orange); + } + } + + Future connectToDevice(BluetoothDevice device) async { + try { + String name = device.platformName.isEmpty ? 'Dispositivo' : device.platformName; + _showSnackBar("Conectando a $name...", Colors.blue); + await device.connect(); + _showSnackBar("Conectado com sucesso!", Colors.green); + } catch (e) { + _showSnackBar("Falha ao conectar: $e", Colors.red); + } + } + + void _showSnackBar(String message, Color color) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: color, + duration: const Duration(seconds: 3), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Conexão Bluetooth'), + backgroundColor: Colors.black87, + foregroundColor: Colors.white, + ), + backgroundColor: Colors.grey[900], + body: Column( + children: [ + const SizedBox(height: 20), + Center( + child: Column( + children: [ + const Icon(Icons.bluetooth, size: 80, color: Colors.blue), + const SizedBox(height: 10), + ElevatedButton.icon( + onPressed: _isScanning ? null : startScan, + icon: _isScanning + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black), + ) + : const Icon(Icons.search), + label: Text(_isScanning ? 'Buscando...' : 'Procurar Dispositivos'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + ), + ), + ], + ), + ), + const SizedBox(height: 20), + Expanded( + child: _scanResults.isEmpty && !_isScanning + ? const Center( + child: Text( + "Clique em Procurar para encontrar dispositivos.\n(Nota: Requer telemóvel físico para Bluetooth real)", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white54), + ), + ) + : ListView.builder( + itemCount: _scanResults.length, + itemBuilder: (context, index) { + final r = _scanResults[index]; + final name = r.device.platformName.isEmpty + ? "Dispositivo Desconhecido" + : r.device.platformName; + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + color: Colors.white10, + child: ListTile( + leading: const Icon(Icons.bluetooth, color: Colors.blue), + title: Text( + name, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + r.device.remoteId.toString(), + style: const TextStyle(color: Colors.white70), + ), + trailing: ElevatedButton( + onPressed: () => connectToDevice(r.device), + child: const Text("Conectar"), + ), + ), + ); + }, + ), + ), + ], + ), + ); + } +}