adonna 0.1.2 copy "adonna: ^0.1.2" to clipboard
adonna: ^0.1.2 copied to clipboard

discontinued

A Flutter plugin that wraps the Feasycom FeasyBeacon SDK for Android and iOS, providing cross-platform BLE beacon functionality with background monitoring capabilities.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:adonna/adonna.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';

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

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

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

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

  @override
  State<AdonnaHomePage> createState() => _AdonnaHomePageState();
}

class _AdonnaHomePageState extends State<AdonnaHomePage> with TickerProviderStateMixin {
  late TabController _tabController;
  bool _isScanning = false;
  bool _isConnected = false;
  bool _isBackgroundMonitoring = false;
  String? _connectedAddress;
  String? _connectedName;
  String _pin = '';
  Map<String, dynamic>? _deviceInfo;

  // Device lists
  final Map<String, Map<String, dynamic>> _scannedDevices = {};
  final Map<String, Map<String, dynamic>> _favoriteDevices = {};

  // Background monitoring
  final TextEditingController _uuidController = TextEditingController();
  final TextEditingController _majorController = TextEditingController();
  final TextEditingController _thresholdController = TextEditingController();
  final List<Map<String, dynamic>> _backgroundEvents = [];

  // iOS background monitoring
  bool _isIosBackgroundMonitoring = false;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    _loadFavorites();
    _setupStreams();
    _ensurePermissions();

    // Set default UUID for testing
    _uuidController.text = '12345678-1234-1234-1234-123456789012';
    _thresholdController.text = '100';
  }

  @override
  void dispose() {
    _tabController.dispose();
    _uuidController.dispose();
    _majorController.dispose();
    _thresholdController.dispose();
    super.dispose();
  }

  void _setupStreams() {
    // Scan events
    FeasyBeacon.scanStream.listen((event) {
      if (event is Map<String, dynamic>) {
        final address = event['address'] as String?;
        if (address != null) {
          setState(() {
            _scannedDevices[address] = Map<String, dynamic>.from(event);
          });
        }
      }
    });

    // Background events
    FeasyBeacon.backgroundEvents.listen((event) {
      if (event is Map<String, dynamic>) {
        setState(() {
          _backgroundEvents.insert(0, Map<String, dynamic>.from(event));
          if (_backgroundEvents.length > 50) {
            _backgroundEvents.removeLast();
          }
        });
      }
    });

    // OTA progress
    FeasyBeacon.otaProgressStream.listen((event) {
      if (event is Map<String, dynamic>) {
        final progress = event['progress'] as int?;
        final status = event['status'] as String?;
        if (progress != null && status != null) {
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('OTA: $status - $progress%')));
        }
      }
    });

    // Device info stream
    FeasyBeacon.deviceInfoStream.listen((event) {
      if (event is Map<String, dynamic>) {
        setState(() {
          _deviceInfo = Map<String, dynamic>.from(event);
        });
      }
    });

    // Packet stream
    FeasyBeacon.packetStream.listen((event) {
      if (event is Map<String, dynamic>) {
        final data = event['data'] as List<int>?;
        if (data != null) {
          final hexData = data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Packet: $hexData')));
        }
      }
    });
  }

  Future<void> _ensurePermissions() async {
    await Permission.bluetooth.request();
    await Permission.location.request();
    await Permission.notification.request();
  }

  Future<void> _loadFavorites() async {
    final prefs = await SharedPreferences.getInstance();
    final favoritesJson = prefs.getString('favorite_devices');
    if (favoritesJson != null) {
      final favorites = Map<String, dynamic>.from(jsonDecode(favoritesJson));
      setState(() {
        _favoriteDevices.clear();
        favorites.forEach((key, value) {
          _favoriteDevices[key] = Map<String, dynamic>.from(value);
        });
      });
    }
  }

  Future<void> _saveFavorites() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('favorite_devices', jsonEncode(_favoriteDevices));
  }

  Future<void> _addToFavorites(String address, Map<String, dynamic> device) async {
    setState(() {
      _favoriteDevices[address] = Map<String, dynamic>.from(device);
    });
    await _saveFavorites();
  }

  Future<void> _removeFromFavorites(String address) async {
    setState(() {
      _favoriteDevices.remove(address);
    });
    await _saveFavorites();
  }

  Future<void> _toggleScan() async {
    if (_isScanning) {
      await FeasyBeacon.stopScan();
      setState(() {
        _isScanning = false;
      });
    } else {
      await FeasyBeacon.startScan();
      setState(() {
        _isScanning = true;
        _scannedDevices.clear();
      });
    }
  }

  Future<void> _connectToDevice(String address) async {
    try {
      final success = await FeasyBeacon.connect(address: address);
      if (success) {
        setState(() {
          _isConnected = true;
          _connectedAddress = address;
          _connectedName = _scannedDevices[address]?['name'] ?? 'Unknown';
        });

        // Add to favorites automatically
        if (_scannedDevices.containsKey(address)) {
          await _addToFavorites(address, _scannedDevices[address]!);
        }

        // Stop scanning when connected
        if (_isScanning) {
          await _toggleScan();
        }

        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Connected successfully!')));
      } else {
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Failed to connect')));
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Connection error: $e')));
    }
  }

  Future<void> _disconnect() async {
    try {
      await FeasyBeacon.disconnect();
      setState(() {
        _isConnected = false;
        _connectedAddress = null;
        _connectedName = null;
        _deviceInfo = null;
      });
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Disconnected')));
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Disconnect error: $e')));
    }
  }

  Future<void> _getDeviceInfo() async {
    if (!_isConnected) return;

    try {
      final info = await FeasyBeacon.getDeviceInfo();
      setState(() {
        _deviceInfo = info;
      });
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Device info loaded')));
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to get device info: $e')));
    }
  }

  Future<void> _toggleBackgroundMonitoring() async {
    if (_isBackgroundMonitoring) {
      await FeasyBeacon.stopBackgroundMonitoring();
      setState(() {
        _isBackgroundMonitoring = false;
      });
    } else {
      final success = await FeasyBeacon.startBackgroundMonitoring(
        config: {
          'uuid': _uuidController.text,
          'major': _majorController.text.isNotEmpty ? int.parse(_majorController.text) : null,
          'thresholdMs': int.parse(_thresholdController.text),
        },
      );
      if (success) {
        setState(() {
          _isBackgroundMonitoring = true;
        });
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Background monitoring started')));
      } else {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(const SnackBar(content: Text('Failed to start background monitoring')));
      }
    }
  }

  Future<void> _toggleIosBackgroundMonitoring() async {
    if (_isIosBackgroundMonitoring) {
      await FeasyBeacon.stopBackgroundMonitoring();
      setState(() {
        _isIosBackgroundMonitoring = false;
      });
    } else {
      final success = await FeasyBeacon.startIosBackgroundMonitoring(
        uuid: _uuidController.text,
        major: _majorController.text.isNotEmpty ? int.parse(_majorController.text) : null,
        thresholdMs: int.parse(_thresholdController.text),
      );
      if (success) {
        setState(() {
          _isIosBackgroundMonitoring = true;
        });
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('iOS background monitoring started')));
      } else {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(const SnackBar(content: Text('Failed to start iOS background monitoring')));
      }
    }
  }

  Future<void> _triggerOta() async {
    if (!_isConnected) return;

    try {
      // Mock OTA file for testing
      final mockFile = File('mock_dfu.bin');
      if (!await mockFile.exists()) {
        await mockFile.writeAsBytes(List.generate(1024, (i) => i % 256));
      }

      final fileBytes = await mockFile.readAsBytes();
      final dfuInfo = await FeasyBeacon.checkDfuFile(fileBytes);
      if (dfuInfo != null) {
        final success = await FeasyBeacon.startOtaUpdate(fileBytes);
        if (success) {
          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('OTA update started')));
        } else {
          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Failed to start OTA update')));
        }
      } else {
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Invalid DFU file')));
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('OTA error: $e')));
    }
  }

  Widget _buildDeviceList() {
    return ListView.builder(
      itemCount: _scannedDevices.length,
      itemBuilder: (context, index) {
        final address = _scannedDevices.keys.elementAt(index);
        final device = _scannedDevices[address]!;
        final isFavorite = _favoriteDevices.containsKey(address);

        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          child: ListTile(
            leading: Icon(isFavorite ? Icons.favorite : Icons.bluetooth, color: isFavorite ? Colors.red : Colors.blue),
            title: Text(device['name'] ?? 'Unknown Device'),
            subtitle: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('MAC: $address'),
                Text('RSSI: ${device['rssi'] ?? 'N/A'}'),
                if (device['beaconTypes'] != null) Text('Types: ${(device['beaconTypes'] as List).join(', ')}'),
              ],
            ),
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                if (_isConnected && _connectedAddress == address)
                  const Icon(Icons.link, color: Colors.green)
                else if (!_isConnected)
                  IconButton(
                    icon: const Icon(Icons.link),
                    onPressed: () => _connectToDevice(address),
                    tooltip: 'Connect',
                  ),
                IconButton(
                  icon: Icon(
                    isFavorite ? Icons.favorite : Icons.favorite_border,
                    color: isFavorite ? Colors.red : null,
                  ),
                  onPressed: () {
                    if (isFavorite) {
                      _removeFromFavorites(address);
                    } else {
                      _addToFavorites(address, device);
                    }
                  },
                  tooltip: isFavorite ? 'Remove from favorites' : 'Add to favorites',
                ),
              ],
            ),
            isThreeLine: true,
          ),
        );
      },
    );
  }

  Widget _buildFavoritesList() {
    return ListView.builder(
      itemCount: _favoriteDevices.length,
      itemBuilder: (context, index) {
        final address = _favoriteDevices.keys.elementAt(index);
        final device = _favoriteDevices[address]!;

        return Dismissible(
          key: Key(address),
          background: Container(
            color: Colors.red,
            alignment: Alignment.centerRight,
            padding: const EdgeInsets.only(right: 16),
            child: const Icon(Icons.delete, color: Colors.white),
          ),
          direction: DismissDirection.endToStart,
          onDismissed: (direction) => _removeFromFavorites(address),
          child: Card(
            margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            child: ListTile(
              leading: const Icon(Icons.favorite, color: Colors.red),
              title: Text(device['name'] ?? 'Unknown Device'),
              subtitle: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('MAC: $address'),
                  Text('RSSI: ${device['rssi'] ?? 'N/A'}'),
                  if (device['beaconTypes'] != null) Text('Types: ${(device['beaconTypes'] as List).join(', ')}'),
                ],
              ),
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (_isConnected && _connectedAddress == address)
                    const Icon(Icons.link, color: Colors.green)
                  else if (!_isConnected)
                    IconButton(
                      icon: const Icon(Icons.link),
                      onPressed: () => _connectToDevice(address),
                      tooltip: 'Connect',
                    ),
                ],
              ),
              isThreeLine: true,
            ),
          ),
        );
      },
    );
  }

  Widget _buildBackgroundMonitoringTab() {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Background Monitoring Configuration',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 16),
                  TextField(
                    controller: _uuidController,
                    decoration: const InputDecoration(labelText: 'iBeacon UUID', border: OutlineInputBorder()),
                  ),
                  const SizedBox(height: 8),
                  TextField(
                    controller: _majorController,
                    decoration: const InputDecoration(labelText: 'Major (optional)', border: OutlineInputBorder()),
                    keyboardType: TextInputType.number,
                  ),
                  const SizedBox(height: 8),
                  TextField(
                    controller: _thresholdController,
                    decoration: const InputDecoration(labelText: 'Threshold (ms)', border: OutlineInputBorder()),
                    keyboardType: TextInputType.number,
                  ),
                  const SizedBox(height: 16),
                  Row(
                    children: [
                      Expanded(
                        child: ElevatedButton(
                          onPressed: _toggleBackgroundMonitoring,
                          style: ElevatedButton.styleFrom(
                            backgroundColor: _isBackgroundMonitoring ? Colors.red : Colors.green,
                          ),
                          child: Text(_isBackgroundMonitoring ? 'Stop Android BG' : 'Start Android BG'),
                        ),
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: ElevatedButton(
                          onPressed: _toggleIosBackgroundMonitoring,
                          style: ElevatedButton.styleFrom(
                            backgroundColor: _isIosBackgroundMonitoring ? Colors.red : Colors.green,
                          ),
                          child: Text(_isIosBackgroundMonitoring ? 'Stop iOS BG' : 'Start iOS BG'),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Expanded(
            child: Card(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Text('Background Events', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                  ),
                  Expanded(
                    child: _backgroundEvents.isEmpty
                        ? const Center(child: Text('No background events yet'))
                        : ListView.builder(
                            itemCount: _backgroundEvents.length,
                            itemBuilder: (context, index) {
                              final event = _backgroundEvents[index];
                              return ListTile(
                                leading: const Icon(Icons.notifications),
                                title: Text('Button Press Detected'),
                                subtitle: Text(
                                  'UUID: ${event['uuid'] ?? 'N/A'}\n'
                                  'Major: ${event['major'] ?? 'N/A'}\n'
                                  'Interval: ${event['intervalMs'] ?? 'N/A'}ms',
                                ),
                                isThreeLine: true,
                              );
                            },
                          ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Adonna Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(icon: Icon(Icons.bluetooth_searching), text: 'Devices'),
            Tab(icon: Icon(Icons.favorite), text: 'Favorites'),
            Tab(icon: Icon(Icons.settings), text: 'Background'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [_buildDeviceList(), _buildFavoritesList(), _buildBackgroundMonitoringTab()],
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _toggleScan,
            tooltip: _isScanning ? 'Stop Scan' : 'Start Scan',
            child: Icon(_isScanning ? Icons.stop : Icons.search),
          ),
          const SizedBox(height: 8),
          if (_isConnected) ...[
            FloatingActionButton(
              onPressed: _disconnect,
              tooltip: 'Disconnect',
              backgroundColor: Colors.red,
              child: const Icon(Icons.link_off),
            ),
            const SizedBox(height: 8),
            FloatingActionButton(
              onPressed: _getDeviceInfo,
              tooltip: 'Get Device Info',
              backgroundColor: Colors.orange,
              child: const Icon(Icons.info),
            ),
            const SizedBox(height: 8),
            FloatingActionButton(
              onPressed: _triggerOta,
              tooltip: 'Trigger OTA',
              backgroundColor: Colors.purple,
              child: const Icon(Icons.system_update),
            ),
          ],
        ],
      ),
      bottomNavigationBar: _isConnected
          ? Container(
              padding: const EdgeInsets.all(16),
              color: Colors.green.shade100,
              child: Row(
                children: [
                  const Icon(Icons.link, color: Colors.green),
                  const SizedBox(width: 8),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Text('Connected to: $_connectedName', style: const TextStyle(fontWeight: FontWeight.bold)),
                        Text('MAC: $_connectedAddress'),
                      ],
                    ),
                  ),
                  if (_deviceInfo != null)
                    IconButton(
                      icon: const Icon(Icons.expand_more),
                      onPressed: () {
                        showDialog(
                          context: context,
                          builder: (context) => AlertDialog(
                            title: const Text('Device Info'),
                            content: SingleChildScrollView(
                              child: Text(const JsonEncoder.withIndent('  ').convert(_deviceInfo)),
                            ),
                            actions: [
                              TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Close')),
                            ],
                          ),
                        );
                      },
                    ),
                ],
              ),
            )
          : null,
    );
  }
}
0
likes
0
points
17
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin that wraps the Feasycom FeasyBeacon SDK for Android and iOS, providing cross-platform BLE beacon functionality with background monitoring capabilities.

Homepage

License

unknown (license)

Dependencies

flutter

More

Packages that depend on adonna

Packages that implement adonna