From e20de5f992a350a1e58bdad288e872590b286d41 Mon Sep 17 00:00:00 2001 From: 240405 <240405@epvc.pt> Date: Thu, 26 Feb 2026 17:35:05 +0000 Subject: [PATCH] =?UTF-8?q?Mudan=C3=A7a=20na=20tela=20bluetooth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bluetooth_screen.dart | 308 ++++++++++++++++++++++++++++---------- 1 file changed, 232 insertions(+), 76 deletions(-) diff --git a/lib/bluetooth_screen.dart b/lib/bluetooth_screen.dart index a5719a8..4cbf610 100644 --- a/lib/bluetooth_screen.dart +++ b/lib/bluetooth_screen.dart @@ -2,6 +2,7 @@ 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}); @@ -20,14 +21,27 @@ class _BluetoothScreenState extends State { 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 = results; + _scanResults = sortedResults; }); } }); + // Ouvir o estado da busca _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) { if (mounted) { setState(() { @@ -37,6 +51,11 @@ class _BluetoothScreenState extends 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(); @@ -45,7 +64,6 @@ class _BluetoothScreenState extends State { } Future startScan() async { - // 1. Pedir permissões Map statuses = await [ Permission.bluetoothScan, Permission.bluetoothConnect, @@ -56,15 +74,16 @@ class _BluetoothScreenState extends State { statuses[Permission.bluetoothConnect]!.isGranted) { try { - // 2. Iniciar Scan + _scanResults.clear(); await FlutterBluePlus.startScan( timeout: const Duration(seconds: 15), + androidUsesFineLocation: true, + continuousUpdates: true, ); - // Se não encontrar nada (ex: no emulador), avisa o usuário - Future.delayed(const Duration(seconds: 5), () { + Future.delayed(const Duration(seconds: 8), () { if (mounted && _scanResults.isEmpty && _isScanning) { - _showSnackBar("Nenhum dispositivo encontrado (Verifique se o Bluetooth/GPS estão ligados)", Colors.orange); + _showSnackBar("Nenhum dispositivo encontrado.", AppColors.coral); } }); @@ -72,14 +91,22 @@ class _BluetoothScreenState extends State { _showSnackBar("Erro ao iniciar busca: $e", Colors.red); } } else { - _showSnackBar("Permissões de Bluetooth negadas", Colors.orange); + _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.isEmpty ? 'Dispositivo' : device.platformName; - _showSnackBar("Conectando a $name...", Colors.blue); + 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) { @@ -87,6 +114,18 @@ class _BluetoothScreenState extends State { } } + // 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( @@ -94,6 +133,7 @@ class _BluetoothScreenState extends State { content: Text(message), backgroundColor: color, duration: const Duration(seconds: 3), + behavior: SnackBarBehavior.floating, ), ); } @@ -102,81 +142,197 @@ class _BluetoothScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Conexão Bluetooth'), - backgroundColor: Colors.black87, - foregroundColor: Colors.white, + title: const Text('Bluetooth', style: TextStyle(fontWeight: FontWeight.bold)), + centerTitle: true, + backgroundColor: Colors.transparent, + elevation: 0, + foregroundColor: AppColors.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), - ), - ), - ], - ), + 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, + ], ), - 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), + ), + 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, + ), ), - ) - : 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, + 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( - color: Colors.white, + fontSize: 14, fontWeight: FontWeight.bold, + letterSpacing: 1.2, ), ), - subtitle: Text( - r.device.remoteId.toString(), - style: const TextStyle(color: Colors.white70), - ), - trailing: ElevatedButton( - onPressed: () => connectToDevice(r.device), - child: const Text("Conectar"), - ), - ), - ); - }, + ], + ), + ), ), - ), - ], + ], + ), + ), + + 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)), + ), + ), + ); + }, + ), + ), + ], + ), ), ); }