// Copyright 2017, Paul DeMarco. // All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; import 'package:flutter_blue_localnotify/widgets.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; void main() { flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); runApp(FlutterBlueApp()); } class FlutterBlueApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( color: Colors.lightBlue, home: StreamBuilder( stream: FlutterBlue.instance.state, initialData: BluetoothState.unknown, builder: (c, snapshot) { final state = snapshot.data; if (state == BluetoothState.on) { return FindDevicesScreen(); } return BluetoothOffScreen(state: state); }), ); } } class BluetoothOffScreen extends StatelessWidget { const BluetoothOffScreen({Key key, this.state}) : super(key: key); final BluetoothState state; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.lightBlue, body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.bluetooth_disabled, size: 200.0, color: Colors.white54, ), Text( 'Bluetooth Adapter is ${state.toString().substring(15)}.', style: Theme.of(context) .primaryTextTheme .subhead .copyWith(color: Colors.white), ), ], ), ), ); } } class FindDevicesScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Find Devices'), ), body: RefreshIndicator( onRefresh: () => FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)), child: SingleChildScrollView( child: Column( children: [ StreamBuilder>( stream: Stream.periodic(Duration(seconds: 2)) .asyncMap((_) => FlutterBlue.instance.connectedDevices), initialData: [], builder: (c, snapshot) => Column( children: snapshot.data .map((d) => ListTile( title: Text(d.name), subtitle: Text(d.id.toString()), trailing: StreamBuilder( stream: d.state, initialData: BluetoothDeviceState.disconnected, builder: (c, snapshot) { if (snapshot.data == BluetoothDeviceState.connected) { return RaisedButton( child: Text('OPEN'), onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => DeviceScreen(device: d))), ); } return Text(snapshot.data.toString()); }, ), )) .toList(), ), ), StreamBuilder>( stream: FlutterBlue.instance.scanResults, initialData: [], builder: (c, snapshot) => Column( children: snapshot.data .map( (r) => ScanResultTile( result: r, onTap: () => Navigator.of(context) .push(MaterialPageRoute(builder: (context) { r.device.connect(); return DeviceScreen(device: r.device); })), ), ) .toList(), ), ), ], ), ), ), floatingActionButton: StreamBuilder( stream: FlutterBlue.instance.isScanning, initialData: false, builder: (c, snapshot) { if (snapshot.data) { return FloatingActionButton( child: Icon(Icons.stop), onPressed: () => FlutterBlue.instance.stopScan(), backgroundColor: Colors.red, ); } else { return FloatingActionButton( child: Icon(Icons.search), onPressed: () => FlutterBlue.instance .startScan(timeout: Duration(seconds: 4))); } }, ), ); } } class DeviceScreen extends StatefulWidget { const DeviceScreen({Key key, this.device}) : super(key: key); final BluetoothDevice device; @override _DeviceScreenState createState() => _DeviceScreenState(); } class _DeviceScreenState extends State { bool isConnected = false; List _buildServiceTiles(List services) { return services .map( (s) => ServiceTile( service: s, characteristicTiles: s.characteristics .map( (c) => CharacteristicTile( characteristic: c, onReadPressed: () => c.read(), onWritePressed: () => c.write([13, 24]), onNotificationPressed: () => c.setNotifyValue(!c.isNotifying), descriptorTiles: c.descriptors .map( (d) => DescriptorTile( descriptor: d, onReadPressed: () => d.read(), onWritePressed: () => d.write([11, 12]), ), ) .toList(), ), ) .toList(), ), ) .toList(); } @override void initState() { super.initState(); FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); // initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project var initializationSettingsAndroid = new AndroidInitializationSettings('app_icon'); var initializationSettingsIOS = new IOSInitializationSettings( onDidReceiveLocalNotification: onDidReceiveLocalNotification); var initializationSettings = new InitializationSettings( initializationSettingsAndroid, initializationSettingsIOS); flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: onSelectNotification); Timer.periodic(new Duration(seconds: 1), (timer) { FlutterBlue.instance.connectedDevices.then((value) { if (value.length >= 1 && !isConnected) { _showNotificationWithNoBadge("${widget.device.name}", "Connected"); isConnected = true; } else if (value.length == 0 && isConnected) { _showNotificationWithNoBadge("${widget.device.name}", "Disconnected"); isConnected = false; } }); }); } Future _showNotificationWithNoBadge(String title, String body) async { var androidPlatformChannelSpecifics = AndroidNotificationDetails( 'no badge channel', 'no badge name', 'no badge description', channelShowBadge: false, importance: Importance.Max, priority: Priority.High, onlyAlertOnce: true); var iOSPlatformChannelSpecifics = IOSNotificationDetails(); var platformChannelSpecifics = NotificationDetails( androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); await flutterLocalNotificationsPlugin .show(0, title, body, platformChannelSpecifics, payload: 'item x'); } Future onDidReceiveLocalNotification( int id, String title, String body, String payload) async { // display a dialog with the notification details, tap ok to go to another page await showDialog( context: context, builder: (BuildContext context) => CupertinoAlertDialog( title: Text(title), content: Text(body), actions: [ CupertinoDialogAction( isDefaultAction: true, child: Text('Ok'), onPressed: () async { Navigator.of(context, rootNavigator: true).pop(); }, ) ], ), ); } Future onSelectNotification(String payload) async { if (payload != null) { debugPrint('notification payload: ' + payload); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.device.name), actions: [ StreamBuilder( stream: widget.device.state, initialData: BluetoothDeviceState.connecting, builder: (c, snapshot) { VoidCallback onPressed; String text; switch (snapshot.data) { case BluetoothDeviceState.connected: onPressed = () => widget.device.disconnect(); text = 'DISCONNECT'; break; case BluetoothDeviceState.disconnected: onPressed = () => widget.device.connect(); text = 'CONNECT'; break; default: onPressed = null; text = snapshot.data.toString().substring(21).toUpperCase(); break; } return FlatButton( onPressed: onPressed, child: Text( text, style: Theme.of(context) .primaryTextTheme .button .copyWith(color: Colors.white), )); }, ) ], ), body: SingleChildScrollView( child: Column( children: [ StreamBuilder( stream: widget.device.state, initialData: BluetoothDeviceState.connecting, builder: (c, snapshot) => ListTile( leading: (snapshot.data == BluetoothDeviceState.connected) ? Icon(Icons.bluetooth_connected) : Icon(Icons.bluetooth_disabled), title: Text( 'Device is ${snapshot.data.toString().split('.')[1]}.'), subtitle: Text('${widget.device.id}'), trailing: StreamBuilder( stream: widget.device.isDiscoveringServices, initialData: false, builder: (c, snapshot) => IndexedStack( index: snapshot.data ? 1 : 0, children: [ IconButton( icon: Icon(Icons.refresh), onPressed: () => widget.device.discoverServices(), ), IconButton( icon: SizedBox( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.grey), ), width: 18.0, height: 18.0, ), onPressed: null, ) ], ), ), ), ), StreamBuilder>( stream: widget.device.services, initialData: [], builder: (c, snapshot) { return Column( children: _buildServiceTiles(snapshot.data), ); }, ), ], ), ), ); } }