bearound_flutter_sdk 2.0.1 copy "bearound_flutter_sdk: ^2.0.1" to clipboard
bearound_flutter_sdk: ^2.0.1 copied to clipboard

Bearound Flutter SDK

example/lib/main.dart

import 'dart:async';

import 'package:bearound_flutter_sdk/bearound_flutter_sdk.dart';
import 'package:flutter/material.dart';

import 'settings_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bearound SDK Example',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const BeaconHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class BeaconHomePage extends StatefulWidget {
  const BeaconHomePage({super.key});

  @override
  State<BeaconHomePage> createState() => _BeaconHomePageState();
}

class _BeaconHomePageState extends State<BeaconHomePage>
    with WidgetsBindingObserver {
  bool _hasPermission = false;
  bool _isScanning = false;
  String _status = 'Parado';

  int _syncIntervalSeconds = 30;
  bool _enableBluetoothScanning = true;
  bool _enablePeriodicScanning = true;

  List<Beacon> _detectedBeacons = [];
  final List<String> _logs = [];
  SyncStatus _syncStatus = const SyncStatus(
    secondsUntilNextSync: 0,
    isRanging: false,
  );
  String? _lastError;

  StreamSubscription<List<Beacon>>? _beaconsSubscription;
  StreamSubscription<SyncStatus>? _syncSubscription;
  StreamSubscription<bool>? _scanningSubscription;
  StreamSubscription<BearoundError>? _errorSubscription;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _startListening();
    _initializeSdk();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _beaconsSubscription?.cancel();
    _syncSubscription?.cancel();
    _scanningSubscription?.cancel();
    _errorSubscription?.cancel();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      _refreshScanningState();
    }
  }

  Future<void> _initializeSdk() async {
    final granted = await BearoundFlutterSdk.requestPermissions();
    setState(() {
      _hasPermission = granted;
      _status = granted ? 'Permissões OK' : 'Permissões necessárias!';
    });

    if (!granted) {
      return;
    }

    await _applyConfiguration();
    await _refreshScanningState();
  }

  Future<void> _applyConfiguration() async {
    try {
      await BearoundFlutterSdk.configure(
        businessToken: "your-business-token-here",
        syncInterval: Duration(seconds: _syncIntervalSeconds),
        enableBluetoothScanning: _enableBluetoothScanning,
        enablePeriodicScanning: _enablePeriodicScanning,
      );

      _addLog(
        '⚙️ Configurado: ${_syncIntervalSeconds}s, '
        'BLE ${_enableBluetoothScanning ? 'on' : 'off'}, '
        'periódico ${_enablePeriodicScanning ? 'on' : 'off'}',
      );
    } catch (error) {
      setState(() {
        _lastError = error.toString();
      });
      _addLog('❌ Erro ao configurar: $error');
    }
  }

  Future<void> _refreshScanningState() async {
    final isRunning = await BearoundFlutterSdk.isScanning();
    setState(() {
      _isScanning = isRunning;
      _status = isRunning ? 'Scaneando…' : 'Parado';
    });
  }

  void _startListening() {
    _beaconsSubscription = BearoundFlutterSdk.beaconsStream.listen((beacons) {
      setState(() {
        _detectedBeacons = beacons;
      });
      _addLog('📡 Beacons detectados: ${beacons.length}');
    });

    _syncSubscription = BearoundFlutterSdk.syncStream.listen((status) {
      setState(() {
        _syncStatus = status;
      });
    });

    _scanningSubscription = BearoundFlutterSdk.scanningStream.listen((
      isScanning,
    ) {
      setState(() {
        _isScanning = isScanning;
        _status = isScanning ? 'Scaneando…' : 'Parado';
      });
      _addLog('🔄 Scanning ${isScanning ? 'ativo' : 'parado'}');
    });

    _errorSubscription = BearoundFlutterSdk.errorStream.listen((error) {
      setState(() {
        _lastError = error.message;
      });
      _addLog('❌ Erro: ${error.message}');
    });
  }

  void _addLog(String log) {
    final timestamp = DateTime.now().toString().substring(11, 19);
    if (!mounted) {
      return;
    }
    setState(() {
      _logs.insert(0, '[$timestamp] $log');
      if (_logs.length > 50) {
        _logs.removeLast();
      }
    });
  }

  Future<void> _startScan() async {
    if (!_hasPermission) {
      _addLog('⚠️ Permissões necessárias');
      return;
    }

    await _applyConfiguration();
    try {
      await BearoundFlutterSdk.startScanning();
      _addLog('🚀 Scanner iniciado');
    } catch (error) {
      setState(() {
        _lastError = error.toString();
      });
      _addLog('❌ Erro ao iniciar: $error');
    }
  }

  Future<void> _stopScan() async {
    try {
      await BearoundFlutterSdk.stopScanning();
    } catch (error) {
      setState(() {
        _lastError = error.toString();
      });
      _addLog('❌ Erro ao parar: $error');
    }
    setState(() {
      _detectedBeacons = [];
      _syncStatus = const SyncStatus(secondsUntilNextSync: 0, isRanging: false);
      _lastError = null;
    });
    _addLog('🛑 Scanner parado');
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Bearound SDK'),
          bottom: const TabBar(
            tabs: [
              Tab(icon: Icon(Icons.radar), text: 'Beacons'),
              Tab(icon: Icon(Icons.sync), text: 'Status'),
              Tab(icon: Icon(Icons.article), text: 'Logs'),
            ],
          ),
          actions: [
            Container(
              margin: const EdgeInsets.only(right: 16),
              child: Center(
                child: Row(
                  children: [
                    Icon(
                      _isScanning ? Icons.wifi_tethering : Icons.wifi_off,
                      color: _isScanning ? Colors.green : Colors.red,
                    ),
                    const SizedBox(width: 8),
                    Text(_status),
                  ],
                ),
              ),
            ),
            IconButton(
              icon: const Icon(Icons.settings),
              onPressed: () async {
                final result = await Navigator.push<SdkSettings>(
                  context,
                  MaterialPageRoute(
                    builder: (context) => SettingsPage(
                      initialSyncIntervalSeconds: _syncIntervalSeconds,
                      initialBluetoothScanning: _enableBluetoothScanning,
                      initialPeriodicScanning: _enablePeriodicScanning,
                    ),
                  ),
                );

                if (result != null) {
                  setState(() {
                    _syncIntervalSeconds = result.syncIntervalSeconds;
                    _enableBluetoothScanning = result.enableBluetoothScanning;
                    _enablePeriodicScanning = result.enablePeriodicScanning;
                  });
                  if (_hasPermission) {
                    await _applyConfiguration();
                  }
                }
              },
              tooltip: 'Configurações',
            ),
          ],
        ),
        body: TabBarView(
          children: [_buildBeaconsTab(), _buildStatusTab(), _buildLogsTab()],
        ),
        bottomNavigationBar: _buildBottomBar(),
      ),
    );
  }

  Widget _buildBeaconsTab() {
    return Column(
      children: [
        if (_lastError != null)
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.red.shade50,
            child: Row(
              children: [
                const Icon(Icons.error_outline, color: Colors.red),
                const SizedBox(width: 8),
                Expanded(
                  child: Text(
                    _lastError!,
                    style: const TextStyle(fontSize: 14, color: Colors.red),
                  ),
                ),
              ],
            ),
          ),
        Expanded(
          child: _detectedBeacons.isEmpty
              ? const Center(
                  child: Text(
                    'Nenhum beacon detectado ainda',
                    style: TextStyle(fontSize: 16, color: Colors.grey),
                  ),
                )
              : ListView.builder(
                  itemCount: _detectedBeacons.length,
                  itemBuilder: (context, index) {
                    final beacon = _detectedBeacons[index];
                    final metadata = beacon.metadata;
                    return Card(
                      margin: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 8,
                      ),
                      child: ListTile(
                        leading: CircleAvatar(
                          backgroundColor: Colors.blue,
                          child: Text('${index + 1}'),
                        ),
                        title: Text(
                          'Major: ${beacon.major} | Minor: ${beacon.minor}',
                        ),
                        subtitle: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text('UUID: ${beacon.uuid}'),
                            Text('RSSI: ${beacon.rssi} dBm'),
                            Text(
                              'Proximidade: ${beacon.proximity.name} | '
                              'Precisão: ${beacon.accuracy.toStringAsFixed(2)}m',
                            ),
                            if (metadata != null)
                              Text(
                                'Bateria: ${metadata.batteryLevel}% | '
                                'Temp: ${metadata.temperature}°C | '
                                'Firmware: ${metadata.firmwareVersion}',
                              ),
                          ],
                        ),
                        isThreeLine: true,
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }

  Widget _buildStatusTab() {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Status do SDK',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          Container(
            decoration: BoxDecoration(
              color: Colors.grey.shade100,
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.grey.shade300),
            ),
            padding: const EdgeInsets.all(16),
            width: double.infinity,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Scanning: ${_isScanning ? 'ativo' : 'parado'}'),
                const SizedBox(height: 8),
                Text('Próxima sync: ${_syncStatus.secondsUntilNextSync}s'),
                const SizedBox(height: 8),
                Text('Ranging: ${_syncStatus.isRanging ? 'ativo' : 'inativo'}'),
              ],
            ),
          ),
          const SizedBox(height: 16),
          const Text(
            'Configuração Atual',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          Text('Intervalo: $_syncIntervalSeconds segundos'),
          Text(
            'Bluetooth metadata: ${_enableBluetoothScanning ? 'ligado' : 'desligado'}',
          ),
          Text(
            'Scan periódico: ${_enablePeriodicScanning ? 'ligado' : 'desligado'}',
          ),
          if (_lastError != null) ...[
            const SizedBox(height: 16),
            Text(
              'Último erro: $_lastError',
              style: const TextStyle(color: Colors.red),
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildLogsTab() {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey.shade100,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text('Total de logs: ${_logs.length}'),
              TextButton.icon(
                onPressed: () {
                  setState(() {
                    _logs.clear();
                  });
                },
                icon: const Icon(Icons.delete),
                label: const Text('Limpar'),
              ),
            ],
          ),
        ),
        Expanded(
          child: _logs.isEmpty
              ? const Center(
                  child: Text(
                    'Nenhum log ainda',
                    style: TextStyle(fontSize: 16, color: Colors.grey),
                  ),
                )
              : ListView.builder(
                  itemCount: _logs.length,
                  itemBuilder: (context, index) {
                    return Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 8,
                      ),
                      decoration: BoxDecoration(
                        border: Border(
                          bottom: BorderSide(color: Colors.grey.shade300),
                        ),
                      ),
                      child: Text(
                        _logs[index],
                        style: const TextStyle(
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }

  Widget _buildBottomBar() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.grey.shade300,
            blurRadius: 4,
            offset: const Offset(0, -2),
          ),
        ],
      ),
      child: SafeArea(
        child: Row(
          children: [
            if (!_hasPermission)
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _initializeSdk,
                  icon: const Icon(Icons.lock_open),
                  label: const Text('Solicitar Permissões'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 12),
                  ),
                ),
              ),
            if (_hasPermission && !_isScanning)
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _startScan,
                  icon: const Icon(Icons.play_arrow),
                  label: const Text('Iniciar Scan'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    backgroundColor: Colors.green,
                    foregroundColor: Colors.white,
                  ),
                ),
              ),
            if (_isScanning)
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _stopScan,
                  icon: const Icon(Icons.stop),
                  label: const Text('Parar Scan'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    backgroundColor: Colors.red,
                    foregroundColor: Colors.white,
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}