This commit is contained in:
ayub 2025-09-06 18:38:40 +07:00
commit 78294f18e2
98 changed files with 3834 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

27
CHANGELOG.md Normal file
View File

@ -0,0 +1,27 @@
## 0.1.7
- Fix null Exception at _$BluetoothDeviceFromJson and BluetoothManager.state (PR https://github.com/andrey-ushakov/flutter_bluetooth_basic/pull/26).
## 0.1.6
- Null-Safety
## 0.1.5
- Updated pubspec.yaml
## 0.1.4
- Android: start scan bug fixed (error handling)
## 0.1.3
- Updated README.md
## 0.1.2
- Added Android support
## 0.0.1
- iOS only support

27
LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2020 Andrey U. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# flutter_bluetooth_basic
Flutter plugin that allows to find bluetooth devices & send raw bytes data.
Supports both Android and iOS.
Inspired by [bluetooth_print](https://github.com/thon-ju/bluetooth_print).
## Main Features
* Android and iOS support
* Scan for bluetooth devices
* Send raw `List<int> bytes` data to a device
## Getting Started
For a full example please check */example* folder. Here are only the most important parts of the code to illustrate how to use the library.
```dart
BluetoothManager bluetoothManager = BluetoothManager.instance;
BluetoothDevice _device;
bluetoothManager.startScan(timeout: Duration(seconds: 4));
bluetoothManager.state.listen((state) {
switch (state) {
case BluetoothManager.CONNECTED:
// ...
break;
case BluetoothManager.DISCONNECTED:
// ...
break;
default:
break;
}
});
// bluetoothManager.scanResults is a Stream<List<BluetoothDevice>> sending the found devices.
// _device = <from bluetoothManager.scanResults>
await bluetoothManager.connect(_device);
List<int> bytes = latin1.encode('Hello world!\n').toList();
await bluetoothManager.writeData(bytes);
await bluetoothManager.disconnect();
```
## See also
* Example of usage in a project: [esc_pos_printer](https://github.com/andrey-ushakov/esc_pos_printer)

BIN
android/.DS_Store vendored Normal file

Binary file not shown.

39
android/build.gradle Normal file
View File

@ -0,0 +1,39 @@
group 'com.tablemi.flutter_bluetooth_basic'
version '1.0'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
android {
namespace 'com.tablemi.flutter_bluetooth_basic'
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
implementation files('libs/gprintersdkv2.jar')
}

View File

@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip

Binary file not shown.

1
android/settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = 'flutter_bluetooth_basic'

View File

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tablemi.flutter_bluetooth_basic">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
</manifest>

View File

@ -0,0 +1,5 @@
package com.tablemi.flutter_bluetooth_basic;
public class Constant {
public static final int abnormal_Disconnection = 0x011; // Abnormal disconnection
}

View File

@ -0,0 +1,643 @@
package com.tablemi.flutter_bluetooth_basic;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbDevice;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.gprinter.io.*;
import java.io.IOException;
import java.util.Vector;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DeviceConnFactoryManager {
public PortManager mPort;
private static final String TAG = DeviceConnFactoryManager.class.getSimpleName();
public CONN_METHOD connMethod;
private String ip;
private int port;
private String macAddress;
private UsbDevice mUsbDevice;
private Context mContext;
private String serialPortPath;
private int baudrate;
private int id;
private static DeviceConnFactoryManager[] deviceConnFactoryManagers = new DeviceConnFactoryManager[4];
private boolean isOpenPort;
/**
* ESC查询打印机实时状态指令 // ESC query printer real-time status instruction
*/
private byte[] esc = {0x10, 0x04, 0x02};
/**
* ESC查询打印机实时状态 缺纸状态
*/
private static final int ESC_STATE_PAPER_ERR = 0x20;
/**
* ESC指令查询打印机实时状态 打印机开盖状态
*/
private static final int ESC_STATE_COVER_OPEN = 0x04;
/**
* ESC指令查询打印机实时状态 打印机报错状态
*/
private static final int ESC_STATE_ERR_OCCURS = 0x40;
/**
* TSC查询打印机状态指令
*/
private byte[] tsc = {0x1b, '!', '?'};
/**
* TSC指令查询打印机实时状态 打印机缺纸状态
*/
private static final int TSC_STATE_PAPER_ERR = 0x04;
/**
* TSC指令查询打印机实时状态 打印机开盖状态
*/
private static final int TSC_STATE_COVER_OPEN = 0x01;
/**
* TSC指令查询打印机实时状态 打印机出错状态
*/
private static final int TSC_STATE_ERR_OCCURS = 0x80;
private byte[] cpcl={0x1b,0x68};
/**
* CPCL指令查询打印机实时状态 打印机缺纸状态
*/
private static final int CPCL_STATE_PAPER_ERR = 0x01;
/**
* CPCL指令查询打印机实时状态 打印机开盖状态
*/
private static final int CPCL_STATE_COVER_OPEN = 0x02;
private byte[] sendCommand;
/**
* 判断打印机所使用指令是否是ESC指令
*/
private PrinterCommand currentPrinterCommand;
public static final byte FLAG = 0x10;
private static final int READ_DATA = 10000;
private static final int DEFAUIT_COMMAND=20000;
private static final String READ_DATA_CNT = "read_data_cnt";
private static final String READ_BUFFER_ARRAY = "read_buffer_array";
public static final String ACTION_CONN_STATE = "action_connect_state";
public static final String ACTION_QUERY_PRINTER_STATE = "action_query_printer_state";
public static final String STATE = "state";
public static final String DEVICE_ID = "id";
public static final int CONN_STATE_DISCONNECT = 0x90;
public static final int CONN_STATE_CONNECTING = CONN_STATE_DISCONNECT << 1;
public static final int CONN_STATE_FAILED = CONN_STATE_DISCONNECT << 2;
public static final int CONN_STATE_CONNECTED = CONN_STATE_DISCONNECT << 3;
public PrinterReader reader;
private int queryPrinterCommandFlag;
private final int ESC = 1;
private final int TSC = 3;
private final int CPCL = 2;
public enum CONN_METHOD {
//蓝牙连接
BLUETOOTH("BLUETOOTH"),
//USB连接
USB("USB"),
//wifi连接
WIFI("WIFI"),
//串口连接
SERIAL_PORT("SERIAL_PORT");
private String name;
private CONN_METHOD(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
public static DeviceConnFactoryManager[] getDeviceConnFactoryManagers() {
return deviceConnFactoryManagers;
}
/**
* 打开端口
*
* @return
*/
public void openPort() {
deviceConnFactoryManagers[id].isOpenPort = false;
switch (deviceConnFactoryManagers[id].connMethod) {
case BLUETOOTH:
mPort = new BluetoothPort(macAddress);
isOpenPort = deviceConnFactoryManagers[id].mPort.openPort();
break;
case USB:
mPort = new UsbPort(mContext, mUsbDevice);
isOpenPort = mPort.openPort();
break;
case WIFI:
mPort = new EthernetPort(ip, port);
isOpenPort = mPort.openPort();
break;
case SERIAL_PORT:
mPort = new SerialPort(serialPortPath, baudrate, 0);
isOpenPort = mPort.openPort();
break;
default:
break;
}
//端口打开成功后检查连接打印机所使用的打印机指令ESCTSC
if (isOpenPort) {
queryCommand();
} else {
if (this.mPort != null) {
this.mPort=null;
}
}
}
/**
* 查询当前连接打印机所使用打印机指令ESCEscCommand.javaTSCLabelCommand.java
*/
private void queryCommand() {
//开启读取打印机返回数据线程
reader = new PrinterReader();
reader.start(); //读取数据线程
//查询打印机所使用指令
queryPrinterCommand(); //小票机连接不上 注释这行添加下面那三行代码使用ESC指令
}
/**
* 获取端口连接方式
*
* @return
*/
public CONN_METHOD getConnMethod() {
return connMethod;
}
/**
* Get port open status (true open, false not open)
*
* @return
*/
public boolean getConnState() {
return isOpenPort;
}
/**
* 获取连接蓝牙的物理地址
*
* @return
*/
public String getMacAddress() {
return macAddress;
}
/**
* 获取连接网口端口号
*
* @return
*/
public int getPort() {
return port;
}
/**
* 获取连接网口的IP
*
* @return
*/
public String getIp() {
return ip;
}
/**
* 获取连接的USB设备信息
*
* @return
*/
public UsbDevice usbDevice() {
return mUsbDevice;
}
/**
* 关闭端口
*/
public void closePort(int id) {
if (this.mPort != null) {
if(reader!=null) {
reader.cancel();
reader = null;
}
boolean b= this.mPort.closePort();
if(b) {
this.mPort=null;
isOpenPort = false;
currentPrinterCommand = null;
}
}
}
/**
* 获取串口号
*
* @return
*/
public String getSerialPortPath() {
return serialPortPath;
}
/**
* 获取波特率
*
* @return
*/
public int getBaudrate() {
return baudrate;
}
public static void closeAllPort() {
for (DeviceConnFactoryManager deviceConnFactoryManager : deviceConnFactoryManagers) {
if (deviceConnFactoryManager != null) {
Log.e(TAG, "cloaseAllPort() id -> " + deviceConnFactoryManager.id);
deviceConnFactoryManager.closePort(deviceConnFactoryManager.id);
deviceConnFactoryManagers[deviceConnFactoryManager.id] = null;
}
}
}
private DeviceConnFactoryManager(Build build) {
this.connMethod = build.connMethod;
this.macAddress = build.macAddress;
this.port = build.port;
this.ip = build.ip;
this.mUsbDevice = build.usbDevice;
this.mContext = build.context;
this.serialPortPath = build.serialPortPath;
this.baudrate = build.baudrate;
this.id = build.id;
deviceConnFactoryManagers[id] = this;
}
/**
* 获取当前打印机指令
*
* @return PrinterCommand
*/
public PrinterCommand getCurrentPrinterCommand() {
return deviceConnFactoryManagers[id].currentPrinterCommand;
}
public static final class Build {
private String ip;
private String macAddress;
private UsbDevice usbDevice;
private int port;
private CONN_METHOD connMethod;
private Context context;
private String serialPortPath;
private int baudrate;
private int id;
public DeviceConnFactoryManager.Build setIp(String ip) {
this.ip = ip;
return this;
}
public DeviceConnFactoryManager.Build setMacAddress(String macAddress) {
this.macAddress = macAddress;
return this;
}
public DeviceConnFactoryManager.Build setUsbDevice(UsbDevice usbDevice) {
this.usbDevice = usbDevice;
return this;
}
public DeviceConnFactoryManager.Build setPort(int port) {
this.port = port;
return this;
}
public DeviceConnFactoryManager.Build setConnMethod(CONN_METHOD connMethod) {
this.connMethod = connMethod;
return this;
}
public DeviceConnFactoryManager.Build setContext(Context context) {
this.context = context;
return this;
}
public DeviceConnFactoryManager.Build setId(int id) {
this.id = id;
return this;
}
public DeviceConnFactoryManager.Build setSerialPort(String serialPortPath) {
this.serialPortPath = serialPortPath;
return this;
}
public DeviceConnFactoryManager.Build setBaudrate(int baudrate) {
this.baudrate = baudrate;
return this;
}
public DeviceConnFactoryManager build() {
return new DeviceConnFactoryManager(this);
}
}
public void sendDataImmediately(final Vector<Byte> data) {
if (this.mPort == null) {
return;
}
try {
this.mPort.writeDataImmediately(data, 0, data.size());
} catch (Exception e) {
// Abort Send
mHandler.obtainMessage(Constant.abnormal_Disconnection).sendToTarget();
e.printStackTrace();
}
}
public void sendByteDataImmediately(final byte [] data) {
if (this.mPort == null) {
return;
}else {
Vector<Byte> datas = new Vector<>();
for(int i = 0; i < data.length; ++i) {
datas.add(Byte.valueOf(data[i]));
}
try {
this.mPort.writeDataImmediately(datas, 0, datas.size());
} catch (IOException e) {
// Abort Send
e.printStackTrace();
mHandler.obtainMessage(Constant.abnormal_Disconnection).sendToTarget();
}
}
}
public int readDataImmediately(byte[] buffer){
int r = 0;
if (this.mPort == null) {
return r;
}
try {
r = this.mPort.readData(buffer);
} catch (IOException e) {
}
return r;
}
/**
* 查询打印机当前使用的指令ESCCPCLTSC
*/
private void queryPrinterCommand() {
queryPrinterCommandFlag = ESC;
ThreadPool.getInstantiation().addSerialTask(new Runnable() {
@Override
public void run() {
//开启计时器隔2000毫秒没有没返回值时发送查询打印机状态指令先发票据面单标签
final ThreadFactoryBuilder threadFactoryBuilder = new ThreadFactoryBuilder("Timer");
final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, threadFactoryBuilder);
scheduledExecutorService.scheduleAtFixedRate(threadFactoryBuilder.newThread(new Runnable() {
@Override
public void run() {
if (currentPrinterCommand == null && queryPrinterCommandFlag > TSC) {
if (reader != null) {//三种状态查询无返回值发送连接失败广播
reader.cancel();
mPort.closePort();
isOpenPort = false;
scheduledExecutorService.shutdown();
}
}
if (currentPrinterCommand != null) {
if (scheduledExecutorService != null && !scheduledExecutorService.isShutdown()) {
scheduledExecutorService.shutdown();
}
return;
}
switch (queryPrinterCommandFlag) {
case ESC:
//发送ESC查询打印机状态指令
sendCommand = esc;
break;
case TSC:
//发送ESC查询打印机状态指令
sendCommand = tsc;
break;
case CPCL:
//发送CPCL查询打印机状态指令
sendCommand = cpcl;
break;
default:
break;
}
Vector<Byte> data = new Vector<>(sendCommand.length);
for (int i = 0; i < sendCommand.length; i++) {
data.add(sendCommand[i]);
}
sendDataImmediately(data);
queryPrinterCommandFlag++;
}
}), 1500, 1500, TimeUnit.MILLISECONDS);
}
});
}
class PrinterReader extends Thread {
private boolean isRun = false;
private byte[] buffer = new byte[100];
public PrinterReader() {
isRun = true;
}
@Override
public void run() {
try {
while (isRun) {
//读取打印机返回信息,打印机没有返回纸返回-1
Log.e(TAG,"wait read ");
int len = readDataImmediately(buffer);
Log.e(TAG," read "+len);
if (len > 0) {
Message message = Message.obtain();
message.what = READ_DATA;
Bundle bundle = new Bundle();
bundle.putInt(READ_DATA_CNT, len); //数据长度
bundle.putByteArray(READ_BUFFER_ARRAY, buffer); //数据
message.setData(bundle);
mHandler.sendMessage(message);
}
}
} catch (Exception e) {//异常断开
if (deviceConnFactoryManagers[id] != null) {
closePort(id);
mHandler.obtainMessage(Constant.abnormal_Disconnection).sendToTarget();
}
}
}
public void cancel() {
isRun = false;
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.abnormal_Disconnection://异常断开连接
Log.d(TAG, "abnormal disconnection");
sendStateBroadcast(Constant.abnormal_Disconnection);
break;
case DEFAUIT_COMMAND://默认模式
break;
case READ_DATA:
int cnt = msg.getData().getInt(READ_DATA_CNT); //数据长度 >0;
byte[] buffer = msg.getData().getByteArray(READ_BUFFER_ARRAY); //数据
//这里只对查询状态返回值做处理其它返回值可参考编程手册来解析
if (buffer == null) {
return;
}
int result = judgeResponseType(buffer[0]); //数据右移
String status = "";
if (sendCommand == esc) {
//设置当前打印机模式为ESC模式
if (currentPrinterCommand == null) {
currentPrinterCommand = PrinterCommand.ESC;
sendStateBroadcast(CONN_STATE_CONNECTED);
} else {//查询打印机状态
if (result == 0) {//打印机状态查询
Intent intent = new Intent(ACTION_QUERY_PRINTER_STATE);
intent.putExtra(DEVICE_ID, id);
if(mContext!=null){
mContext.sendBroadcast(intent);
}
} else if (result == 1) {//查询打印机实时状态
if ((buffer[0] & ESC_STATE_PAPER_ERR) > 0) {
status += " Printer out of paper";
}
if ((buffer[0] & ESC_STATE_COVER_OPEN) > 0) {
status += " Printer open cover";
}
if ((buffer[0] & ESC_STATE_ERR_OCCURS) > 0) {
status += " Printer error";
}
Log.d(TAG, status);
}
}
}else if (sendCommand == tsc) {
//设置当前打印机模式为TSC模式
if (currentPrinterCommand == null) {
currentPrinterCommand = PrinterCommand.TSC;
sendStateBroadcast(CONN_STATE_CONNECTED);
} else {
if (cnt == 1) {//查询打印机实时状态
if ((buffer[0] & TSC_STATE_PAPER_ERR) > 0) {//缺纸
status += " Printer out of paper";
}
if ((buffer[0] & TSC_STATE_COVER_OPEN) > 0) {//开盖
status += " Printer open cover";
}
if ((buffer[0] & TSC_STATE_ERR_OCCURS) > 0) {//打印机报错
status += " Printer error";
}
Log.d(TAG, status);
} else {//打印机状态查询
Intent intent = new Intent(ACTION_QUERY_PRINTER_STATE);
intent.putExtra(DEVICE_ID, id);
if(mContext!=null){
mContext.sendBroadcast(intent);
}
}
}
}else if(sendCommand==cpcl){
if (currentPrinterCommand == null) {
currentPrinterCommand = PrinterCommand.CPCL;
sendStateBroadcast(CONN_STATE_CONNECTED);
}else {
if (cnt == 1) {
if ((buffer[0] ==CPCL_STATE_PAPER_ERR)) {//缺纸
status += " Printer out of paper";
}
if ((buffer[0] ==CPCL_STATE_COVER_OPEN)) {//开盖
status += " Printer open cover";
}
Log.d(TAG, status);
} else {//打印机状态查询
Intent intent = new Intent(ACTION_QUERY_PRINTER_STATE);
intent.putExtra(DEVICE_ID, id);
if(mContext!=null){
mContext.sendBroadcast(intent);
}
}
}
}
break;
default:
break;
}
}
};
/**
* 发送广播
* @param state
*/
private void sendStateBroadcast(int state) {
Intent intent = new Intent(ACTION_CONN_STATE);
intent.putExtra(STATE, state);
intent.putExtra(DEVICE_ID, id);
if(mContext != null){
mContext.sendBroadcast(intent);//此处若报空指针错误需要在清单文件application标签里注册此类参考demo
}
}
/**
* 判断是实时状态10 04 02还是查询状态1D 72 01
*/
private int judgeResponseType(byte r) {
return (byte) ((r & FLAG) >> 4);
}
}

View File

@ -0,0 +1,318 @@
package com.tablemi.flutter_bluetooth_basic;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
public class FlutterBluetoothBasicPlugin implements
FlutterPlugin,
MethodChannel.MethodCallHandler,
PluginRegistry.RequestPermissionsResultListener,
ActivityAware {
private static final String TAG = "BluetoothBasicPlugin";
private static final String NAMESPACE = "flutter_bluetooth_basic";
private static final int REQUEST_COARSE_LOCATION_PERMISSIONS = 1451;
private MethodChannel channel;
private EventChannel stateChannel;
private Context context;
private Activity activity;
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private ThreadPool threadPool;
private MethodCall pendingCall;
private MethodChannel.Result pendingResult;
private int id = 0;
@Override
public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
context = flutterPluginBinding.getApplicationContext();
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), NAMESPACE + "/methods");
stateChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), NAMESPACE + "/state");
channel.setMethodCallHandler(this);
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
channel = null;
stateChannel.setStreamHandler(null);
stateChannel = null;
}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
this.activity = binding.getActivity();
binding.addRequestPermissionsResultListener(this);
mBluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (stateChannel != null) {
stateChannel.setStreamHandler(stateStreamHandler);
}
}
@Override
public void onDetachedFromActivityForConfigChanges() {
activity = null;
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}
@Override
public void onDetachedFromActivity() {
activity = null;
}
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (mBluetoothAdapter == null && !"isAvailable".equals(call.method)) {
result.error("bluetooth_unavailable", "Bluetooth is unavailable", null);
return;
}
final Map<String, Object> args = call.arguments();
switch (call.method) {
case "state":
state(result);
break;
case "isAvailable":
result.success(mBluetoothAdapter != null);
break;
case "isOn":
result.success(mBluetoothAdapter.isEnabled());
break;
case "isConnected":
result.success(threadPool != null);
break;
case "startScan":
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
activity,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_COARSE_LOCATION_PERMISSIONS);
pendingCall = call;
pendingResult = result;
break;
}
startScan(call, result);
break;
case "stopScan":
stopScan();
result.success(null);
break;
case "connect":
connect(result, args);
break;
case "disconnect":
result.success(disconnect());
break;
case "destroy":
result.success(destroy());
break;
case "writeData":
writeData(result, args);
break;
default:
result.notImplemented();
break;
}
}
private void startScan(MethodCall call, MethodChannel.Result result) {
try {
startScan();
result.success(null);
} catch (Exception e) {
result.error("startScan", e.getMessage(), null);
}
}
private void state(MethodChannel.Result result) {
try {
result.success(mBluetoothAdapter.getState());
} catch (SecurityException e) {
result.error("invalid_argument", "Argument 'address' not found", null);
}
}
private void startScan() throws IllegalStateException {
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
if (scanner == null)
throw new IllegalStateException("getBluetoothLeScanner() is null. Is the Adapter on?");
ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
scanner.startScan(null, settings, mScanCallback);
}
private void stopScan() {
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
if (scanner != null) scanner.stopScan(mScanCallback);
}
private final ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
if (device != null && device.getName() != null) {
invokeMethodUIThread("ScanResult", device);
}
}
};
private void invokeMethodUIThread(final String name, final BluetoothDevice device) {
final Map<String, Object> ret = new HashMap<>();
ret.put("address", device.getAddress());
ret.put("name", device.getName());
ret.put("type", device.getType());
activity.runOnUiThread(() -> channel.invokeMethod(name, ret));
}
private void connect(MethodChannel.Result result, Map<String, Object> args) {
if (args.containsKey("address")) {
String address = (String) args.get("address");
disconnect();
new DeviceConnFactoryManager.Build()
.setId(id)
.setConnMethod(DeviceConnFactoryManager.CONN_METHOD.BLUETOOTH)
.setMacAddress(address)
.build();
threadPool = ThreadPool.getInstantiation();
threadPool.addSerialTask(() -> DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].openPort());
result.success(true);
} else {
result.error("invalid_argument", "Argument 'address' not found", null);
}
}
private boolean disconnect() {
if (DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id] != null &&
DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].mPort != null) {
DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].reader.cancel();
DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].mPort.closePort();
DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].mPort = null;
}
return true;
}
private boolean destroy() {
DeviceConnFactoryManager.closeAllPort();
if (threadPool != null) {
threadPool.stopThreadPool();
}
return true;
}
private void writeData(MethodChannel.Result result, Map<String, Object> args) {
if (args.containsKey("bytes")) {
final ArrayList<Integer> bytes = (ArrayList<Integer>) args.get("bytes");
threadPool = ThreadPool.getInstantiation();
threadPool.addSerialTask(() -> {
Vector<Byte> vectorData = new Vector<>();
for (Integer val : bytes) {
vectorData.add(Byte.valueOf(Integer.toString(val > 127 ? val - 256 : val)));
}
DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].sendDataImmediately(vectorData);
});
} else {
result.error("bytes_empty", "Bytes param is empty", null);
}
}
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_COARSE_LOCATION_PERMISSIONS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startScan(pendingCall, pendingResult);
} else {
pendingResult.error("no_permissions", "This app requires location permissions for scanning", null);
pendingResult = null;
}
return true;
}
return false;
}
private final EventChannel.StreamHandler stateStreamHandler = new EventChannel.StreamHandler() {
private EventChannel.EventSink sink;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Log.d(TAG, "stateStreamHandler, current action: " + action);
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
threadPool = null;
sink.success(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
} else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
sink.success(1);
} else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
threadPool = null;
sink.success(0);
}
}
};
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
sink = eventSink;
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
activity.registerReceiver(mReceiver, filter);
}
@Override
public void onCancel(Object o) {
sink = null;
activity.unregisterReceiver(mReceiver);
}
};
}

View File

@ -0,0 +1,7 @@
package com.tablemi.flutter_bluetooth_basic;
public enum PrinterCommand {
ESC,
TSC,
CPCL
}

View File

@ -0,0 +1,21 @@
package com.tablemi.flutter_bluetooth_basic;
import java.util.concurrent.ThreadFactory;
public class ThreadFactoryBuilder implements ThreadFactory {
private String name;
private int counter;
public ThreadFactoryBuilder(String name) {
this.name = name;
counter = 1;
}
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, name);
thread.setName("ThreadFactoryBuilder_" + name + "_" + counter);
return thread;
}
}

View File

@ -0,0 +1,102 @@
package com.tablemi.flutter_bluetooth_basic;
import android.util.Log;
import java.util.ArrayDeque;
import java.util.concurrent.*;
public class ThreadPool {
private Runnable mActive;
private static ThreadPool threadPool;
/**
* java线程池
*/
private ThreadPoolExecutor threadPoolExecutor;
/**
* 系统最大可用线程
*/
private final static int CPU_AVAILABLE = Runtime.getRuntime().availableProcessors();
/**
* 最大线程数
*/
private final static int MAX_POOL_COUNTS = CPU_AVAILABLE * 2 + 1;
/**
* 线程存活时间
*/
private final static long AVAILABLE = 1L;
/**
* 核心线程数
*/
private final static int CORE_POOL_SIZE = CPU_AVAILABLE + 1;
/**
* 线程池缓存队列
*/
private BlockingQueue<Runnable> mWorkQueue = new ArrayBlockingQueue<>(CORE_POOL_SIZE);
private ArrayDeque<Runnable> mArrayDeque = new ArrayDeque<>();
private ThreadFactory threadFactory = new ThreadFactoryBuilder("ThreadPool");
private ThreadPool() {
threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_COUNTS, AVAILABLE, TimeUnit.SECONDS, mWorkQueue,threadFactory);
}
public static ThreadPool getInstantiation() {
if (threadPool == null) {
threadPool = new ThreadPool();
}
return threadPool;
}
public void addParallelTask(Runnable runnable) { //并行线程
if (runnable == null) {
throw new NullPointerException("addTask(Runnable runnable)传入参数为空");
}
if (threadPoolExecutor.getActiveCount()<MAX_POOL_COUNTS) {
Log.i("Lee","目前有"+threadPoolExecutor.getActiveCount()+"个线程正在进行中,有"+mWorkQueue.size()+"个任务正在排队");
synchronized (this){
threadPoolExecutor.execute(runnable);
}
}
}
public synchronized void addSerialTask(final Runnable r) { //串行线程
if (r == null) {
throw new NullPointerException("addTask(Runnable runnable)传入参数为空");
}
mArrayDeque.offer(new Runnable() {
@Override
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
// 第一次入队列时mActivie为空因此需要手动调用scheduleNext方法
if (mActive == null) {
scheduleNext();
}
}
private void scheduleNext() {
if ((mActive = mArrayDeque.poll()) != null) {
threadPoolExecutor.execute(mActive);
}
}
public void stopThreadPool() {
if (threadPoolExecutor != null) {
threadPoolExecutor.shutdown();
threadPoolExecutor = null;
threadPool = null;
}
}
}

BIN
example/.DS_Store vendored Normal file

Binary file not shown.

16
example/README.md Normal file
View File

@ -0,0 +1,16 @@
# flutter_bluetooth_basic_example
Demonstrates how to use the flutter_bluetooth_basic plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,61 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.tablemi.flutter_bluetooth_basic_example"
minSdkVersion 21
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tablemi.flutter_bluetooth_basic_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tablemi.flutter_bluetooth_basic_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_bluetooth_basic_example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,13 @@
package com.tablemi.flutter_bluetooth_basic_example;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tablemi.flutter_bluetooth_basic_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,29 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

View File

@ -0,0 +1,2 @@
sdk.dir=/Users/andrey/Library/Android/sdk
flutter.sdk=/Users/andrey/development/flutter

View File

@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>

View File

@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -0,0 +1,13 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/andrey/development/flutter
FLUTTER_APPLICATION_PATH=/Users/andrey/code/projects/flutter/flutter_bluetooth_basic/example
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=false
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.packages

View File

@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@ -0,0 +1,13 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/andrey/development/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/andrey/code/projects/flutter/flutter_bluetooth_basic/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=false"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.packages"

87
example/ios/Podfile Normal file
View File

@ -0,0 +1,87 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

28
example/ios/Podfile.lock Normal file
View File

@ -0,0 +1,28 @@
PODS:
- Flutter (1.0.0)
- flutter_bluetooth_basic (0.0.1):
- Flutter
- path_provider (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_bluetooth_basic (from `.symlinks/plugins/flutter_bluetooth_basic/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_bluetooth_basic:
:path: ".symlinks/plugins/flutter_bluetooth_basic/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_bluetooth_basic: 0e4e27e22b50b3a25cc1d1e131953feb4af414f4
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271
COCOAPODS: 1.8.4

View File

@ -0,0 +1,579 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
92AC2DEF0FE1AD4381FF46F0 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EBFC51A13E362D761AD92AB2 /* libPods-Runner.a */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
89FC27FEE05299E830AC2A76 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B365B4D6871B56C59378C336 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
EBFC51A13E362D761AD92AB2 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
F3316B1877E9B2F711FD6F98 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
92AC2DEF0FE1AD4381FF46F0 /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
230AA67EDFEF05D8734536AD /* Pods */ = {
isa = PBXGroup;
children = (
F3316B1877E9B2F711FD6F98 /* Pods-Runner.debug.xcconfig */,
89FC27FEE05299E830AC2A76 /* Pods-Runner.release.xcconfig */,
B365B4D6871B56C59378C336 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
4BC1EA12D25798CAE0ED6C20 /* Frameworks */ = {
isa = PBXGroup;
children = (
EBFC51A13E362D761AD92AB2 /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
230AA67EDFEF05D8734536AD /* Pods */,
4BC1EA12D25798CAE0ED6C20 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
D8BFF256A8E2C306C228A524 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
224C8CE2EFE4315A68DB0D58 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = 8YA3WTV6AF;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
224C8CE2EFE4315A68DB0D58 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
D8BFF256A8E2C306C228A524 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8YA3WTV6AF;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.tablemi.flutterBluetoothBasicExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8YA3WTV6AF;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.tablemi.flutterBluetoothBasicExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8YA3WTV6AF;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.tablemi.flutterBluetoothBasicExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@ -0,0 +1,13 @@
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

View File

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */

View File

@ -0,0 +1,28 @@
//
// Generated file. Do not edit.
//
// clang-format off
#import "GeneratedPluginRegistrant.h"
#if __has_include(<flutter_bluetooth_basic/FlutterBluetoothBasicPlugin.h>)
#import <flutter_bluetooth_basic/FlutterBluetoothBasicPlugin.h>
#else
@import flutter_bluetooth_basic;
#endif
#if __has_include(<path_provider/FLTPathProviderPlugin.h>)
#import <path_provider/FLTPathProviderPlugin.h>
#else
@import path_provider;
#endif
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[FlutterBluetoothBasicPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterBluetoothBasicPlugin"]];
[FLTPathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTPathProviderPlugin"]];
}
@end

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_bluetooth_basic_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Allow App use bluetooth?</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Allow App use bluetooth?</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,9 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

202
example/lib/main.dart Normal file
View File

@ -0,0 +1,202 @@
import 'dart:convert';
// import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bluetooth scanner',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Bluetooth Scanner'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
BluetoothManager bluetoothManager = BluetoothManager.instance;
bool _connected = false;
BluetoothDevice _device;
String tips = 'no device connect';
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => initBluetooth());
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initBluetooth() async {
bluetoothManager.startScan(timeout: Duration(seconds: 4));
bool isConnected = await bluetoothManager.isConnected;
bluetoothManager.state.listen((state) {
print('cur device status: $state');
switch (state) {
case BluetoothManager.CONNECTED:
setState(() {
_connected = true;
tips = 'connect success';
});
break;
case BluetoothManager.DISCONNECTED:
setState(() {
_connected = false;
tips = 'disconnect success';
});
break;
default:
break;
}
});
if (!mounted) return;
if (isConnected) {
setState(() {
_connected = true;
});
}
}
void _onConnect() async {
if (_device != null && _device.address != null) {
await bluetoothManager.connect(_device);
} else {
setState(() {
tips = 'please select device';
});
print('please select device');
}
}
void _onDisconnect() async {
await bluetoothManager.disconnect();
}
void _sendData() async {
List<int> bytes = latin1.encode('Hello world!\n\n\n').toList();
// Set codetable west. Add import 'dart:typed_data';
// List<int> bytes = Uint8List.fromList(List.from('\x1Bt'.codeUnits)..add(6));
// Text with special characters
// bytes += latin1.encode('blåbærgrød\n\n\n');
await bluetoothManager.writeData(bytes);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: RefreshIndicator(
onRefresh: () =>
bluetoothManager.startScan(timeout: Duration(seconds: 4)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
child: Text(tips),
),
],
),
Divider(),
StreamBuilder<List<BluetoothDevice>>(
stream: bluetoothManager.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map((d) => ListTile(
title: Text(d.name ?? ''),
subtitle: Text(d.address),
onTap: () async {
setState(() {
_device = d;
});
},
trailing:
_device != null && _device.address == d.address
? Icon(
Icons.check,
color: Colors.green,
)
: null,
))
.toList(),
),
),
Divider(),
Container(
padding: EdgeInsets.fromLTRB(20, 5, 20, 10),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlineButton(
child: Text('connect'),
onPressed: _connected ? null : _onConnect,
),
SizedBox(width: 10.0),
OutlineButton(
child: Text('disconnect'),
onPressed: _connected ? _onDisconnect : null,
),
],
),
OutlineButton(
child: Text('Send test data'),
onPressed: _connected ? _sendData : null,
),
],
),
)
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: bluetoothManager.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => bluetoothManager.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () =>
bluetoothManager.startScan(timeout: Duration(seconds: 4)));
}
},
),
);
}
}

22
example/pubspec.yaml Normal file
View File

@ -0,0 +1,22 @@
name: flutter_bluetooth_basic_example
description: Demonstrates how to use the flutter_bluetooth_basic plugin.
publish_to: 'none'
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
path_provider: ^1.4.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_bluetooth_basic:
path: ../
flutter:
uses-material-design: true

View File

@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_bluetooth_basic_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart Packages" level="project" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
</component>
</module>

102
ios/Classes/BLEConnecter.h Normal file
View File

@ -0,0 +1,102 @@
//
// Connecter.h
// GSDK
//
#import "Connecter.h"
#import <CoreBluetooth/CoreBluetooth.h>
@interface BLEConnecter :Connecter
@property(nonatomic,strong)CBCharacteristic *airPatchChar;
@property(nonatomic,strong)CBCharacteristic *transparentDataWriteChar;
@property(nonatomic,strong)CBCharacteristic *transparentDataReadOrNotifyChar;
@property(nonatomic,strong)CBCharacteristic *connectionParameterChar;
@property(nonatomic,strong)CBUUID *transServiceUUID;
@property(nonatomic,strong)CBUUID *transTxUUID;
@property(nonatomic,strong)CBUUID *transRxUUID;
@property(nonatomic,strong)CBUUID *disUUID1;
@property(nonatomic,strong)CBUUID *disUUID2;
@property(nonatomic,strong)NSArray *serviceUUID;
@property(nonatomic,copy)DiscoverDevice discover;
@property(nonatomic,copy)UpdateState updateState;
@property(nonatomic,copy)WriteProgress writeProgress;
/**数据包大小默认130个字节*/
@property(nonatomic,assign)NSUInteger datagramSize;
@property(nonatomic,strong)CBPeripheral *connPeripheral;
//+(instancetype)sharedInstance;
/**
* :Service UUIDService对应的具有读
* @param serviceUUID service uuid
* @param txUUID
* @param rxUUID
*/
- (void)configureTransparentServiceUUID: (NSString *)serviceUUID txUUID:(NSString *)txUUID rxUUID:(NSString *)rxUUID;
/**
*
* @param serviceUUIDs UUID
* @param options
* @param discover
* peripheral
* advertisementData
* RSSI
*/
-(void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options discover:(void(^_Nullable)(CBPeripheral *_Nullable peripheral,NSDictionary<NSString *, id> *_Nullable advertisementData,NSNumber *_Nullable RSSI))discover;
/**
*
*/
-(void)stopScan;
/**
*
* @param state
*/
-(void)didUpdateState:(void(^_Nullable)(NSInteger state))state;
/**
*
* @param peripheral
* @param options
* @param timeout
* @param connectState
*/
-(void)connectPeripheral:(CBPeripheral *_Nullable)peripheral options:(nullable NSDictionary<NSString *,id> *)options timeout:(NSUInteger)timeout connectBlack:(void(^_Nullable)(ConnectState state)) connectState;
/**
*
* @param peripheral
* @param options
*/
-(void)connectPeripheral:(CBPeripheral * _Nullable)peripheral options:(nullable NSDictionary<NSString *,id> *)options;
/**
*
* @param peripheral
*/
-(void)closePeripheral:(nonnull CBPeripheral *)peripheral;
/**
* : // Method description: write data to Bluetooth module
* @param data // Data written to the Bluetooth module
* @param progress // Progress of writing data
* @param callBack // Read Bluetooth module data
*/
-(void)write:(NSData *_Nullable)data progress:(void(^_Nullable)(NSUInteger total,NSUInteger progress))progress receCallBack:(void (^_Nullable)(NSData *_Nullable))callBack;
/**
* : // Method description: write data to Bluetooth module
* @param characteristic
* @param data // Data written to the Bluetooth module
* @param type <b>CBCharacteristicWriteWithResponse</b><b>CBCharacteristicWriteWithoutResponse</b> <p><b>@see CBCharacteristicWriteType</b></p>
* Writing method <b>CBCharacteristicWriteWithResponse</b> The writing method is a writing method with flow control. <b>CBCharacteristicWriteWithoutResponse</b>Without flow control write method <p><b>@see CBCharacteristicWriteType</b></p>
*/
-(void)writeValue:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;
@end

44
ios/Classes/Connecter.h Normal file
View File

@ -0,0 +1,44 @@
//
// Connecter.h
// GSDK
//
#import <Foundation/Foundation.h>
#import "ConnecterBlock.h"
@interface Connecter:NSObject
//读取数据
@property(nonatomic,copy)ReadData readData;
//连接状态
@property(nonatomic,copy)ConnectDeviceState state;
/**
* : // Method description: connect
*/
-(void)connect;
/**
* : // Method description: connect to the specified device
* @param connectState
*/
-(void)connect:(void(^)(ConnectState state))connectState;
/**
* :
*/
-(void)close;
/**
* // send data
* // Write data to the output stream
*/
-(void)write:(NSData *)data receCallBack:(void(^)(NSData *data))callBack;
-(void)write:(NSData *)data;
/**
*
* @parma data
*/
-(void)read:(void(^)(NSData *data))data;
@end

View File

@ -0,0 +1,40 @@
//
// ConnecterBlock.h
// GSDK
//
#ifndef ConnecterBlock_h
#define ConnecterBlock_h
#import <CoreBluetooth/CBPeripheral.h>
/**
* @enum ConnectState
* @discussion
* @constant CONNECT_STATE_DISCONNECT ConnectDeviceState返回state为该状态是表示已断开连接
* @constant CONNECT_STATE_CONNECTING ConnectDeviceState返回state为该状态是表示正在连接中
* @constant CONNECT_STATE_CONNECTED ConnectDeviceState返回state为该状态是表示连接成功
* @constant CONNECT_STATE_TIMEOUT ConnectDeviceState返回state为该状态是表示连接超时
* @constant CONNECT_STATE_FAILT ConnectDeviceState返回state为该状态是表示连接失败
*/
typedef enum : NSUInteger {
NOT_FOUND_DEVICE,//未找到设备
CONNECT_STATE_DISCONNECT,//断开连接
CONNECT_STATE_CONNECTING,//连接中
CONNECT_STATE_CONNECTED,//连接上
CONNECT_STATE_TIMEOUT,//连接超时
CONNECT_STATE_FAILT//连接失败
}ConnectState;
/**发现设备 Discover devices*/
typedef void(^DiscoverDevice)(CBPeripheral *_Nullable peripheral,NSDictionary<NSString *, id> * _Nullable advertisementData,NSNumber * _Nullable RSSI);
/**蓝牙状态更新 Bluetooth status update*/
typedef void(^UpdateState)(NSInteger state);
/**连接状态 Connection Status*/
typedef void(^ConnectDeviceState)(ConnectState state);
/**读取数据 Read data*/
typedef void(^ReadData)(NSData * _Nullable data);
/**发送数据进度 只适用于蓝牙发送数据 Sending data progress ** Only for Bluetooth sending data*/
typedef void(^WriteProgress)(NSUInteger total,NSUInteger progress);
typedef void (^Error)(id error);
#endif /* ConnecterBlock_h */

View File

@ -0,0 +1,93 @@
//
// ConnecterManager.h
// GSDK
//
#import <Foundation/Foundation.h>
#import "BLEConnecter.h"
#import "EthernetConnecter.h"
#import "Connecter.h"
/**
* @enum ConnectMethod
* @discussion
* @constant BLUETOOTH
* @constant ETHERNET wifi连接
*/
typedef enum : NSUInteger{
BLUETOOTH,
ETHERNET
}ConnectMethod;
#define Manager [ConnecterManager sharedInstance]
@interface ConnecterManager : NSObject
@property(nonatomic,strong)BLEConnecter *bleConnecter;
@property(nonatomic,strong)Connecter *connecter;
+(instancetype)sharedInstance;
/**
*
*/
-(void)close;
/**
* : // Method description: write data to the output stream (only for Bluetooth)
* @param data
* @param progress
* @param callBack
*/
-(void)write:(NSData *_Nullable)data progress:(void(^_Nullable)(NSUInteger total,NSUInteger progress))progress receCallBack:(void (^_Nullable)(NSData *_Nullable))callBack;
/**
* // Method description: writing data to the output stream
* @param callBack
*/
-(void)write:(NSData *)data receCallBack:(void (^)(NSData *))callBack;
/**
* // Method description: writing data to the output stream
* @param data
*/
-(void)write:(NSData *)data;
/**
*
*/
-(void)stopScan;
/**
*
* @param state
*/
-(void)didUpdateState:(void(^)(NSInteger state))state;
/**
*
* @param peripheral
* @param options
* @param timeout
* @param connectState
*/
-(void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *,id> *)options timeout:(NSUInteger)timeout connectBlack:(void(^_Nullable)(ConnectState state)) connectState;
/**
*
* @param peripheral
* @param options
*/
-(void)connectPeripheral:(CBPeripheral * _Nullable)peripheral options:(nullable NSDictionary<NSString *,id> *)options;
/**
*
* @param serviceUUIDs UUIDnil则发现周围所有外设
* @param options
* @param discover
*/
-(void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options discover:(void(^_Nullable)(CBPeripheral *_Nullable peripheral,NSDictionary<NSString *, id> *_Nullable advertisementData,NSNumber *_Nullable RSSI))discover;
@end

View File

@ -0,0 +1,108 @@
//
// ConnecterManager.m
// GSDK
//
//
#import "ConnecterManager.h"
@interface ConnecterManager(){
ConnectMethod currentConnMethod;
}
@end
@implementation ConnecterManager
static ConnecterManager *manager;
static dispatch_once_t once;
+(instancetype)sharedInstance {
dispatch_once(&once, ^{
manager = [[ConnecterManager alloc]init];
});
return manager;
}
/**
*
* @param serviceUUIDs UUIDnil
* @param options
* @param discover
*/
-(void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options discover:(void(^_Nullable)(CBPeripheral *_Nullable peripheral,NSDictionary<NSString *, id> *_Nullable advertisementData,NSNumber *_Nullable RSSI))discover{
[_bleConnecter scanForPeripheralsWithServices:serviceUUIDs options:options discover:discover];
}
/**
*
* @param state
*/
-(void)didUpdateState:(void(^)(NSInteger state))state {
if (_bleConnecter == nil) {
currentConnMethod = BLUETOOTH;
[self initConnecter:currentConnMethod];
}
[_bleConnecter didUpdateState:state];
}
-(void)initConnecter:(ConnectMethod)connectMethod {
switch (connectMethod) {
case BLUETOOTH:
_bleConnecter = [BLEConnecter new];
_connecter = _bleConnecter;
break;
default:
break;
}
}
/**
*
*/
-(void)stopScan {
[_bleConnecter stopScan];
}
/**
*
*/
-(void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *,id> *)options timeout:(NSUInteger)timeout connectBlack:(void(^_Nullable)(ConnectState state)) connectState{
[_bleConnecter connectPeripheral:peripheral options:options timeout:timeout connectBlack:connectState];
}
-(void)connectPeripheral:(CBPeripheral * _Nullable)peripheral options:(nullable NSDictionary<NSString *,id> *)options {
[_bleConnecter connectPeripheral:peripheral options:options];
}
-(void)write:(NSData *_Nullable)data progress:(void(^_Nullable)(NSUInteger total,NSUInteger progress))progress receCallBack:(void (^_Nullable)(NSData *_Nullable))callBack {
[_bleConnecter write:data progress:progress receCallBack:callBack];
}
-(void)write:(NSData *)data receCallBack:(void (^)(NSData *))callBack {
#ifdef DEBUG
NSLog(@"[ConnecterManager] write:receCallBack:");
#endif
_bleConnecter.writeProgress = nil;
[_connecter write:data receCallBack:callBack];
}
-(void)write:(NSData *)data {
#ifdef DEBUG
NSLog(@"[ConnecterManager] write:");
#endif
_bleConnecter.writeProgress = nil;
[_connecter write:data];
}
-(void)close {
if (_connecter) {
[_connecter close];
}
switch (currentConnMethod) {
case BLUETOOTH:
_bleConnecter = nil;
break;
}
}
@end

View File

@ -0,0 +1,27 @@
//
// EthernetConnecter.h
// GSDK
//
#import "Connecter.h"
@interface EthernetConnecter :Connecter
/**连接设备的ip地址*/
@property(nonatomic,strong)NSString *ip;
/**连接设备的端口号*/
@property(nonatomic,assign)int port;
//+(instancetype)sharedInstance;
/**
* :
* @param ip ip地址
* @param port
* @param connectState @see ConnectState
* @param callback
*/
-(void)connectIP:(NSString *)ip port:(int)port connectState:(void (^)(ConnectState state))connectState callback:(void(^)(NSData *data))callback;
-(void)connectIP:(NSString *)ip port:(int)port connectState:(void (^)(ConnectState))connectState;
@end

View File

@ -0,0 +1,13 @@
#import <Flutter/Flutter.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import "ConnecterManager.h"
#define NAMESPACE @"flutter_bluetooth_basic"
@interface FlutterBluetoothBasicPlugin : NSObject<FlutterPlugin, CBCentralManagerDelegate, CBPeripheralDelegate>
@property(nonatomic,copy)ConnectDeviceState state;
@end
@interface BluetoothPrintStreamHandler : NSObject<FlutterStreamHandler>
@property FlutterEventSink sink;
@end

View File

@ -0,0 +1,183 @@
#import "FlutterBluetoothBasicPlugin.h"
#import "ConnecterManager.h"
@interface FlutterBluetoothBasicPlugin ()
@property(nonatomic, retain) NSObject<FlutterPluginRegistrar> *registrar;
@property(nonatomic, retain) FlutterMethodChannel *channel;
@property(nonatomic, retain) BluetoothPrintStreamHandler *stateStreamHandler;
@property(nonatomic) NSMutableDictionary *scannedPeripherals;
@end
@implementation FlutterBluetoothBasicPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:NAMESPACE @"/methods"
binaryMessenger:[registrar messenger]];
FlutterEventChannel* stateChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/state" binaryMessenger:[registrar messenger]];
FlutterBluetoothBasicPlugin* instance = [[FlutterBluetoothBasicPlugin alloc] init];
instance.channel = channel;
instance.scannedPeripherals = [NSMutableDictionary new];
// STATE
BluetoothPrintStreamHandler* stateStreamHandler = [[BluetoothPrintStreamHandler alloc] init];
[stateChannel setStreamHandler:stateStreamHandler];
instance.stateStreamHandler = stateStreamHandler;
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
NSLog(@"call method -> %@", call.method);
if ([@"state" isEqualToString:call.method]) {
result(nil);
} else if([@"isAvailable" isEqualToString:call.method]) {
result(@(YES));
} else if([@"isConnected" isEqualToString:call.method]) {
result(@(NO));
} else if([@"isOn" isEqualToString:call.method]) {
result(@(YES));
}else if([@"startScan" isEqualToString:call.method]) {
NSLog(@"getDevices method -> %@", call.method);
[self.scannedPeripherals removeAllObjects];
if (Manager.bleConnecter == nil) {
[Manager didUpdateState:^(NSInteger state) {
switch (state) {
case CBCentralManagerStateUnsupported:
NSLog(@"The platform/hardware doesn't support Bluetooth Low Energy.");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"The app is not authorized to use Bluetooth Low Energy.");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"Bluetooth is currently powered off.");
break;
case CBCentralManagerStatePoweredOn:
[self startScan];
NSLog(@"Bluetooth power on");
break;
case CBCentralManagerStateUnknown:
default:
break;
}
}];
} else {
[self startScan];
}
result(nil);
} else if([@"stopScan" isEqualToString:call.method]) {
[Manager stopScan];
result(nil);
} else if([@"connect" isEqualToString:call.method]) {
NSDictionary *device = [call arguments];
@try {
NSLog(@"connect device begin -> %@", [device objectForKey:@"name"]);
CBPeripheral *peripheral = [_scannedPeripherals objectForKey:[device objectForKey:@"address"]];
self.state = ^(ConnectState state) {
[self updateConnectState:state];
};
[Manager connectPeripheral:peripheral options:nil timeout:2 connectBlack: self.state];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"disconnect" isEqualToString:call.method]) {
@try {
[Manager close];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"writeData" isEqualToString:call.method]) {
@try {
NSDictionary *args = [call arguments];
NSMutableArray *bytes = [args objectForKey:@"bytes"];
NSNumber* lenBuf = [args objectForKey:@"length"];
int len = [lenBuf intValue];
char cArray[len];
for (int i = 0; i < len; ++i) {
// NSLog(@"** ind_%d (d): %@, %d", i, bytes[i], [bytes[i] charValue]);
cArray[i] = [bytes[i] charValue];
}
NSData *data2 = [NSData dataWithBytes:cArray length:sizeof(cArray)];
// NSLog(@"bytes in hex: %@", [data2 description]);
[Manager write:data2];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
}
}
-(void)startScan {
[Manager scanForPeripheralsWithServices:nil options:nil discover:^(CBPeripheral * _Nullable peripheral, NSDictionary<NSString *,id> * _Nullable advertisementData, NSNumber * _Nullable RSSI) {
if (peripheral.name != nil) {
NSLog(@"find device -> %@", peripheral.name);
[self.scannedPeripherals setObject:peripheral forKey:[[peripheral identifier] UUIDString]];
NSDictionary *device = [NSDictionary dictionaryWithObjectsAndKeys:peripheral.identifier.UUIDString,@"address",peripheral.name,@"name",nil,@"type",nil];
[_channel invokeMethod:@"ScanResult" arguments:device];
}
}];
}
-(void)updateConnectState:(ConnectState)state {
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *ret = @0;
switch (state) {
case CONNECT_STATE_CONNECTING:
NSLog(@"status -> %@", @"Connecting ...");
ret = @0;
break;
case CONNECT_STATE_CONNECTED:
NSLog(@"status -> %@", @"Connection success");
ret = @1;
break;
case CONNECT_STATE_FAILT:
NSLog(@"status -> %@", @"Connection failed");
ret = @0;
break;
case CONNECT_STATE_DISCONNECT:
NSLog(@"status -> %@", @"Disconnected");
ret = @0;
break;
default:
NSLog(@"status -> %@", @"Connection timed out");
ret = @0;
break;
}
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:ret,@"id",nil];
if(_stateStreamHandler.sink != nil) {
self.stateStreamHandler.sink([dict objectForKey:@"id"]);
}
});
}
@end
@implementation BluetoothPrintStreamHandler
- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
self.sink = eventSink;
return nil;
}
- (FlutterError*)onCancelWithArguments:(id)arguments {
self.sink = nil;
return nil;
}
@end

View File

@ -0,0 +1,28 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint flutter_bluetooth_basic.podspec' to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'flutter_bluetooth_basic'
s.version = '0.0.1'
s.summary = 'A new flutter plugin project.'
s.description = <<-DESC
A new flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.static_framework = true
s.dependency 'Flutter'
# s.platform = :ios, '8.0'
# Import all * .a libraries in the Classes folder
s.frameworks = ["SystemConfiguration", "CoreTelephony","WebKit"]
s.vendored_libraries = '**/*.a'
# Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
# s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
end

BIN
ios/libGSDK.a Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
/*
* flutter_bluetooth_basic
* Created by Andrey U.
*
* See LICENSE for distribution and usage details.
*/
library flutter_bluetooth_basic;
export './src/bluetooth_manager.dart';
export './src/bluetooth_device.dart';

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'bluetooth_device.g.dart';
@JsonSerializable(includeIfNull: false)
class BluetoothDevice {
BluetoothDevice();
String? name;
String? address;
int? type = 0;
bool? connected = false;
factory BluetoothDevice.fromJson(Map<String, dynamic> json) =>
_$BluetoothDeviceFromJson(json);
Map<String, dynamic> toJson() => _$BluetoothDeviceToJson(this);
}

View File

@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'bluetooth_device.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BluetoothDevice _$BluetoothDeviceFromJson(Map<String, dynamic> json) {
return BluetoothDevice()
..name = json['name'] as String?
..address = json['address'] as String?
..type = json['type'] as int?
..connected = json['connected'] as bool?;
}
Map<String, dynamic> _$BluetoothDeviceToJson(BluetoothDevice instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('name', instance.name);
writeNotNull('address', instance.address);
writeNotNull('type', instance.type);
writeNotNull('connected', instance.connected);
return val;
}

View File

@ -0,0 +1,143 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:rxdart/rxdart.dart';
import 'bluetooth_device.dart';
/// A BluetoothManager.
class BluetoothManager {
static const String NAMESPACE = 'flutter_bluetooth_basic';
static const int CONNECTED = 1;
static const int DISCONNECTED = 0;
static const MethodChannel _channel =
const MethodChannel('$NAMESPACE/methods');
static const EventChannel _stateChannel =
const EventChannel('$NAMESPACE/state');
Stream<MethodCall> get _methodStream => _methodStreamController.stream;
final StreamController<MethodCall> _methodStreamController =
StreamController.broadcast();
BluetoothManager._() {
_channel.setMethodCallHandler((MethodCall call) {
_methodStreamController.add(call);
return Future(() => null);
});
}
static BluetoothManager _instance = BluetoothManager._();
static BluetoothManager get instance => _instance;
// Future<bool> get isAvailable async =>
// await _channel.invokeMethod('isAvailable').then<bool>((d) => d);
// Future<bool> get isOn async =>
// await _channel.invokeMethod('isOn').then<bool>((d) => d);
Future<bool> get isConnected async =>
await _channel.invokeMethod('isConnected');
BehaviorSubject<bool> _isScanning = BehaviorSubject.seeded(false);
Stream<bool> get isScanning => _isScanning.stream;
BehaviorSubject<List<BluetoothDevice>> _scanResults =
BehaviorSubject.seeded([]);
Stream<List<BluetoothDevice>> get scanResults => _scanResults.stream;
PublishSubject _stopScanPill = new PublishSubject();
/// Gets the current state of the Bluetooth module
Stream<int?> get state async* {
yield await _channel.invokeMethod('state').then((s) => s);
yield* _stateChannel.receiveBroadcastStream().map((s) => s);
}
/// Starts a scan for Bluetooth Low Energy devices
/// Timeout closes the stream after a specified [Duration]
Stream<BluetoothDevice> scan({
Duration? timeout,
}) async* {
if (_isScanning.value == true) {
throw Exception('Another scan is already in progress.');
}
// Emit to isScanning
_isScanning.add(true);
final killStreams = <Stream>[];
killStreams.add(_stopScanPill);
if (timeout != null) {
killStreams.add(Rx.timer(null, timeout));
}
// Clear scan results list
_scanResults.add(<BluetoothDevice>[]);
try {
await _channel.invokeMethod('startScan');
} catch (e) {
print('Error starting scan.');
_stopScanPill.add(null);
_isScanning.add(false);
throw e;
}
yield* BluetoothManager.instance._methodStream
.where((m) => m.method == "ScanResult")
.map((m) => m.arguments)
.takeUntil(Rx.merge(killStreams))
.doOnDone(stopScan)
.map((map) {
final device = BluetoothDevice.fromJson(Map<String, dynamic>.from(map));
final List<BluetoothDevice>? list = _scanResults.value;
int newIndex = -1;
list!.asMap().forEach((index, e) {
if (e.address == device.address) {
newIndex = index;
}
});
if (newIndex != -1) {
list[newIndex] = device;
} else {
list.add(device);
}
_scanResults.add(list);
return device;
});
}
Future startScan({
Duration? timeout,
}) async {
await scan(timeout: timeout).drain();
return _scanResults.value;
}
/// Stops a scan for Bluetooth Low Energy devices
Future stopScan() async {
await _channel.invokeMethod('stopScan');
_stopScanPill.add(null);
_isScanning.add(false);
}
Future<dynamic> connect(BluetoothDevice device) =>
_channel.invokeMethod('connect', device.toJson());
Future<dynamic> disconnect() => _channel.invokeMethod('disconnect');
Future<dynamic> destroy() => _channel.invokeMethod('destroy');
Future<dynamic> writeData(List<int> bytes) {
Map<String, Object> args = Map();
args['bytes'] = bytes;
args['length'] = bytes.length;
_channel.invokeMethod('writeData', args);
return Future.value(true);
}
}

29
pubspec.yaml Normal file
View File

@ -0,0 +1,29 @@
name: flutter_bluetooth_basic
description: Flutter plugin that allows to find bluetooth devices & send raw bytes data
version: 0.1.7
homepage: https://github.com/andrey-ushakov/flutter_bluetooth_basic
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.12.0"
dependencies:
flutter:
sdk: flutter
json_annotation: ^4.1.0
rxdart: ^0.26.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.0.0
json_serializable: ^3.2.2
flutter:
plugin:
platforms:
android:
package: com.tablemi.flutter_bluetooth_basic
pluginClass: FlutterBluetoothBasicPlugin
ios:
pluginClass: FlutterBluetoothBasicPlugin

View File

@ -0,0 +1,19 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_bluetooth_basic/flutter_bluetooth_basic.dart';
void main() {
const MethodChannel channel = MethodChannel('flutter_bluetooth_basic');
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
}