Initial commit

This commit is contained in:
Foti Dim
2024-01-24 14:23:31 +01:00
commit 1c21373478
185 changed files with 14867 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
name: Code Review
permissions:
contents: read
pull-requests: write
on:
workflow_dispatch:
pull_request:
types: [opened, reopened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: anc95/ChatGPT-CodeReview@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# Optional
OPENAI_API_ENDPOINT: https://api.openai.com/v1
MODEL: gpt-3.5-turbo-1106 # https://platform.openai.com/docs/models
PROMPT: "Please check if there are any bugs, confusions, or irregularities in the following code diff: "
top_p: 1 # https://platform.openai.com/docs/api-reference/chat/create#chat/create-top_p
temperature: 1 # https://platform.openai.com/docs/api-reference/chat/create#chat/create-temperature
max_tokens: 4096
MAX_PATCH_LENGTH: 10000 # if the patch/diff length is large than MAX_PATCH_LENGTH, will be ignored and won't review. By default, with no MAX_PATCH_LENGTH set, there is also no limit for the patch/diff length.
+30
View File
@@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
+42
View File
@@ -0,0 +1,42 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: android
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: ios
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: linux
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: macos
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: windows
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+21
View File
@@ -0,0 +1,21 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.20348.0",
"compilerPath": "cl.exe",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "windows-msvc-x64"
}
],
"version": 4
}
+24
View File
@@ -0,0 +1,24 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "example",
"cwd": "example",
"request": "launch",
"type": "dart"
},
{
"name": "example_auto_connect",
"cwd": "example",
"request": "launch",
"type": "dart",
"args": [
"--dart-define",
"AUTO_CONNECT=true",
]
}
]
}
+106
View File
@@ -0,0 +1,106 @@
{
"cSpell.words": [
"connnection",
"DSIMPLEBLE",
"Inspectable",
"microtask",
"modalias",
"NSUUID",
"pairable",
"rssi",
"simpleble",
"unawaited",
"Unpair",
"unwatch",
"uuidstr"
],
"cmake.configureOnOpen": false,
"files.associations": {
"xstring": "cpp",
"chrono": "cpp",
"xlocale": "cpp",
"optional": "cpp",
"memory": "cpp",
"algorithm": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"exception": "cpp",
"format": "cpp",
"forward_list": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"iterator": "cpp",
"limits": "cpp",
"list": "cpp",
"locale": "cpp",
"map": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"set": "cpp",
"shared_mutex": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"string": "cpp",
"system_error": "cpp",
"thread": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"unordered_map": "cpp",
"utility": "cpp",
"variant": "cpp",
"vector": "cpp",
"xfacet": "cpp",
"xhash": "cpp",
"xiosbase": "cpp",
"xlocbuf": "cpp",
"xlocinfo": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"xmemory": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp",
"deque": "cpp",
"fstream": "cpp",
"unordered_set": "cpp",
"complex": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"filesystem": "cpp",
"ranges": "cpp",
"span": "cpp",
"stack": "cpp",
"typeindex": "cpp",
"future": "cpp",
"regex": "cpp"
}
}
+3
View File
@@ -0,0 +1,3 @@
## 0.8.0
* Initial release.
+31
View File
@@ -0,0 +1,31 @@
BSD 3-Clause License
Copyright (c) 2024, Navideck Labs OÜ
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.
Copyright (c) 2020, Woodemi Co,Ltd.
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 HOLDER 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.
+176
View File
@@ -0,0 +1,176 @@
# Universal Ble
A cross-platform (Android/iOS/MacOS/Windows/Linux/Web) BluetoothLE plugin for Flutter
# Usage
- [Receive BLE availability changes](#receive-ble-availability-changes)
- [Scan BLE peripheral](#scan-ble-peripheral)
- [Connect BLE peripheral](#connect-ble-peripheral)
- [Discover services of BLE peripheral](#discover-services-of-ble-peripheral)
- [Transfer data between BLE central & peripheral](#transfer-data-between-ble-central--peripheral)
- [Pair BLE peripheral](#pair-ble-central--peripheral)
| API | Android | iOS | macOS | Windows (beta) | Linux (beta) | Web |
| :------------------- | :-----: | :-: | :---: | :-----: | :---: | :-: |
| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| getConnectedDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| pair/unPair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
| onPairStateChange | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
| enableBluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
| onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
## Getting Started
### Android
You need to add the following permissions to your AndroidManifest.xml file:
```xml
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
```
If you use `BLUETOOTH_SCAN` to determine location, modify your AndroidManifest.xml file to include the following entry:
```xml
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" tools:remove="android:usesPermissionFlags" tools:targetApi="s" />
```
If you use location services in your app, remove `android:maxSdkVersion="30"` from the location permission tags
### iOS / MacOS
Add `NSBluetoothPeripheralUsageDescription` and `NSBluetoothAlwaysUsageDescription` in Info.plist of your iOS and MacOS app,
Add `Bluetooth` capability to MacOS app from Xcode
## Scan BLE peripheral
Android/iOS/macOS/Windows/Linux
```dart
UniversalBle.onScanResult = (scanResult) {
// Handle scan result
}
UniversalBle.startScan();
// To scan on web, you can optionally add filters, and `optionalServices` required to discover those services after connection, for more info check [this](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice)
UniversalBle.startScan(
webRequestOptions: WebRequestOptionsBuilder.acceptAllDevices(
optionalServices: [
"SERVICE_UUID",
],
)
);
UniversalBle.stopScan();
```
## Connect to BLE peripheral
Connect to `deviceId`, received from `UniversalBle.onScanResult`
```dart
UniversalBle.onConnectionChanged = (String deviceId, BleConnectionState state) {
print('onConnectionChanged $deviceId, $state');
}
UniversalBle.connect(deviceId);
UniversalBle.disconnect(deviceId);
```
Get connected devices ( These devices might be connected through another applications, connect using `connect` method to use in your application ), use `withServices` to filter devices
```dart
await UniversalBle.getConnectedDevices(withServices: []);
```
## Discover services of BLE peripheral
Discover services of `deviceId`
```dart
UniversalBle.discoverServices(deviceId);
```
## Transfer data between BLE central & peripheral
- Pull data from peripheral of `deviceId`
```dart
UniversalBle.readValue(deviceId, serviceId, characteristicId);
```
- Send data to peripheral of `deviceId`
```dart
UniversalBle.writeValue(deviceId, serviceId, characteristicId, value);
```
- Receive data from peripheral of `deviceId`
```dart
UniversalBle.onValueChanged = (String deviceId, String characteristicId, Uint8List value) {
print('onValueChanged $deviceId, $characteristicId, ${hex.encode(value)}');
}
// To get characteristic updates in `onValueChanged`
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.notification);
// To stop updates
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.disabled);
```
## Pair BLE central & peripheral
- Pair apis
```dart
UniversalBle.onPairStateChange = (String deviceId, bool isPaired, String? error) {
// Handle Pairing state change
}
// Get result in [onPairStateChange]
UniversalBle.pair(deviceId);
UniversalBle.unPair(deviceId);
UniversalBle.isPaired(deviceId);
```
## Enable bluetooth programatically
```dart
UniversalBle.enableBluetooth();
```
## Receive BLE availability changes
```dart
UniversalBle.onAvailabilityChange = (state) {
// Handle ble availability states
// e.g. poweredOff or poweredOn,
};
```
## To set your own implementation of UniversalBle for any specific platform
```dart
// Create a class which extends `UniversalBlePlatform`
class UniversalBleMock extends UniversalBlePlatform {
// Implement all methods
}
UniversalBle.setInstance(UniversalBleMock());
```
+4
View File
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+9
View File
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
+68
View File
@@ -0,0 +1,68 @@
group 'com.navideck.universal_ble'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
if (project.android.hasProperty("namespace")) {
namespace 'com.navideck.universal_ble'
}
compileSdkVersion 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
}
defaultConfig {
minSdkVersion 21
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}
+1
View File
@@ -0,0 +1 @@
rootProject.name = 'universal_ble'
+10
View File
@@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.navideck.universal_ble">
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
</manifest>
@@ -0,0 +1,598 @@
// Autogenerated from Pigeon (v13.0.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
package com.navideck.universal_ble
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
private fun wrapError(exception: Throwable): List<Any?> {
if (exception is FlutterError) {
return listOf(
exception.code,
exception.message,
exception.details
)
} else {
return listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleScanResult (
val deviceId: String,
val name: String? = null,
val isPaired: Boolean? = null,
val rssi: Long? = null,
val manufacturerData: ByteArray? = null,
val manufacturerDataHead: ByteArray? = null
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): UniversalBleScanResult {
val deviceId = list[0] as String
val name = list[1] as String?
val isPaired = list[2] as Boolean?
val rssi = list[3].let { if (it is Int) it.toLong() else it as Long? }
val manufacturerData = list[4] as ByteArray?
val manufacturerDataHead = list[5] as ByteArray?
return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerData, manufacturerDataHead)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
deviceId,
name,
isPaired,
rssi,
manufacturerData,
manufacturerDataHead,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleService (
val uuid: String,
val characteristics: List<UniversalBleCharacteristic?>? = null
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): UniversalBleService {
val uuid = list[0] as String
val characteristics = list[1] as List<UniversalBleCharacteristic?>?
return UniversalBleService(uuid, characteristics)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
uuid,
characteristics,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleCharacteristic (
val uuid: String,
val properties: List<Long?>
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): UniversalBleCharacteristic {
val uuid = list[0] as String
val properties = list[1] as List<Long?>
return UniversalBleCharacteristic(uuid, properties)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
uuid,
properties,
)
}
}
@Suppress("UNCHECKED_CAST")
private object UniversalBlePlatformChannelCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
128.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleCharacteristic.fromList(it)
}
}
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleScanResult.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleService.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is UniversalBleCharacteristic -> {
stream.write(128)
writeValue(stream, value.toList())
}
is UniversalBleScanResult -> {
stream.write(129)
writeValue(stream, value.toList())
}
is UniversalBleService -> {
stream.write(130)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/**
* Flutter -> Native
*
* Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
interface UniversalBlePlatformChannel {
fun getBluetoothAvailabilityState(callback: (Result<Long>) -> Unit)
fun enableBluetooth(callback: (Result<Boolean>) -> Unit)
fun startScan()
fun stopScan()
fun connect(deviceId: String)
fun disconnect(deviceId: String)
fun setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Long)
fun discoverServices(deviceId: String, callback: (Result<List<UniversalBleService>>) -> Unit)
fun readValue(deviceId: String, service: String, characteristic: String, callback: (Result<ByteArray>) -> Unit)
fun requestMtu(deviceId: String, expectedMtu: Long, callback: (Result<Long>) -> Unit)
fun writeValue(deviceId: String, service: String, characteristic: String, value: ByteArray, bleOutputProperty: Long, callback: (Result<Unit>) -> Unit)
fun isPaired(deviceId: String, callback: (Result<Boolean>) -> Unit)
fun pair(deviceId: String)
fun unPair(deviceId: String)
fun getConnectedDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
companion object {
/** The codec used by UniversalBlePlatformChannel. */
val codec: MessageCodec<Any?> by lazy {
UniversalBlePlatformChannelCodec
}
/** Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binaryMessenger`. */
@Suppress("UNCHECKED_CAST")
fun setUp(binaryMessenger: BinaryMessenger, api: UniversalBlePlatformChannel?) {
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.getBluetoothAvailabilityState() { result: Result<Long> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.enableBluetooth() { result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.startScan()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.stopScan()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
var wrapped: List<Any?>
try {
api.connect(deviceIdArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
var wrapped: List<Any?>
try {
api.disconnect(deviceIdArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val serviceArg = args[1] as String
val characteristicArg = args[2] as String
val bleInputPropertyArg = args[3].let { if (it is Int) it.toLong() else it as Long }
var wrapped: List<Any?>
try {
api.setNotifiable(deviceIdArg, serviceArg, characteristicArg, bleInputPropertyArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
api.discoverServices(deviceIdArg) { result: Result<List<UniversalBleService>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val serviceArg = args[1] as String
val characteristicArg = args[2] as String
api.readValue(deviceIdArg, serviceArg, characteristicArg) { result: Result<ByteArray> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val expectedMtuArg = args[1].let { if (it is Int) it.toLong() else it as Long }
api.requestMtu(deviceIdArg, expectedMtuArg) { result: Result<Long> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val serviceArg = args[1] as String
val characteristicArg = args[2] as String
val valueArg = args[3] as ByteArray
val bleOutputPropertyArg = args[4].let { if (it is Int) it.toLong() else it as Long }
api.writeValue(deviceIdArg, serviceArg, characteristicArg, valueArg, bleOutputPropertyArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
api.isPaired(deviceIdArg) { result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
var wrapped: List<Any?>
try {
api.pair(deviceIdArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
var wrapped: List<Any?>
try {
api.unPair(deviceIdArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val withServicesArg = args[0] as List<String>
api.getConnectedDevices(withServicesArg) { result: Result<List<UniversalBleScanResult>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@Suppress("UNCHECKED_CAST")
private object UniversalBleCallbackChannelCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
128.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleScanResult.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is UniversalBleScanResult -> {
stream.write(128)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/**
* Native -> Flutter
*
* Generated class from Pigeon that represents Flutter messages that can be called from Kotlin.
*/
@Suppress("UNCHECKED_CAST")
class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger) {
companion object {
/** The codec used by UniversalBleCallbackChannel. */
val codec: MessageCodec<Any?> by lazy {
UniversalBleCallbackChannelCodec
}
}
fun onAvailabilityChanged(stateArg: Long, callback: (Result<Unit>) -> Unit) {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged", codec)
channel.send(listOf(stateArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
} else {
callback(Result.success(Unit));
}
} else {
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
}
}
}
fun onPairStateChange(deviceIdArg: String, isPairedArg: Boolean, errorArg: String?, callback: (Result<Unit>) -> Unit) {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange", codec)
channel.send(listOf(deviceIdArg, isPairedArg, errorArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
} else {
callback(Result.success(Unit));
}
} else {
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
}
}
}
fun onScanResult(resultArg: UniversalBleScanResult, callback: (Result<Unit>) -> Unit) {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult", codec)
channel.send(listOf(resultArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
} else {
callback(Result.success(Unit));
}
} else {
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
}
}
}
fun onValueChanged(deviceIdArg: String, characteristicIdArg: String, valueArg: ByteArray, callback: (Result<Unit>) -> Unit) {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged", codec)
channel.send(listOf(deviceIdArg, characteristicIdArg, valueArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
} else {
callback(Result.success(Unit));
}
} else {
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
}
}
}
fun onConnectionChanged(deviceIdArg: String, stateArg: Long, callback: (Result<Unit>) -> Unit) {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged", codec)
channel.send(listOf(deviceIdArg, stateArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
} else {
callback(Result.success(Unit));
}
} else {
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
}
}
}
}
@@ -0,0 +1,212 @@
package com.navideck.universal_ble
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED
import android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
import android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED
import android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR
import android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES
import android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY
import android.bluetooth.le.ScanResult
import android.util.Log
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
private const val TAG = "UniversalBlePlugin"
val knownGatts = mutableListOf<BluetoothGatt>()
enum class BleConnectionState(val value: Long) {
Connected(0),
Disconnected(1)
}
enum class AvailabilityState(val value: Long) {
Unknown(0),
Resetting(1),
Unsupported(2),
Unauthorized(3),
PoweredOff(4),
PoweredOn(5);
}
enum class BleInputProperty(val value: Long) {
Disabled(0),
Notification(1),
Indication(2);
}
enum class BleOutputProperty(val value: Long) {
withResponse(0),
withoutResponse(1);
}
enum class CharacteristicProperty(val value: Long) {
Broadcast(0),
Read(1),
WriteWithoutResponse(2),
Write(3),
Notify(4),
Indicate(5),
AuthenticatedSignedWrites(6),
ExtendedProperties(7)
}
fun String.toBluetoothGatt(): BluetoothGatt {
return knownGatts.find { it.device.address == this }
?: throw FlutterError("IllegalArgument", "Unknown deviceId: $this", null)
}
fun Int.toAvailabilityState(): Long {
return when (this) {
BluetoothAdapter.STATE_OFF -> AvailabilityState.PoweredOff.value
BluetoothAdapter.STATE_ON -> AvailabilityState.PoweredOn.value
BluetoothAdapter.STATE_TURNING_ON -> AvailabilityState.Resetting.value
BluetoothAdapter.STATE_TURNING_OFF -> AvailabilityState.Resetting.value
else -> AvailabilityState.Unknown.value
}
}
fun Int.parseScanErrorMessage(): String {
return when (this) {
SCAN_FAILED_ALREADY_STARTED -> "SCAN_FAILED_ALREADY_STARTED"
SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED"
SCAN_FAILED_FEATURE_UNSUPPORTED -> "SCAN_FAILED_FEATURE_UNSUPPORTED"
SCAN_FAILED_INTERNAL_ERROR -> "SCAN_FAILED_INTERNAL_ERROR"
SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> "SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES"
SCAN_FAILED_SCANNING_TOO_FREQUENTLY -> "SCAN_FAILED_SCANNING_TOO_FREQUENTLY"
else -> "ErrorCode: $this"
}
}
fun Int.parseBluetoothStatusCodeError(): String? {
if (this == BluetoothStatusCodes.SUCCESS) return null
return when (this) {
BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED -> "ERROR_BLUETOOTH_NOT_ENABLED"
BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED -> "ERROR_BLUETOOTH_NOT_ALLOWED"
BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED -> "ERROR_DEVICE_NOT_BONDED"
BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED -> "ERROR_GATT_WRITE_NOT_ALLOWED"
BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY -> "ERROR_GATT_WRITE_REQUEST_BUSY"
BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION -> "ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION"
BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND -> "ERROR_PROFILE_SERVICE_NOT_BOUND"
BluetoothStatusCodes.ERROR_UNKNOWN -> "ERROR_UNKNOWN"
BluetoothStatusCodes.FEATURE_NOT_CONFIGURED -> "FEATURE_NOT_CONFIGURED"
BluetoothStatusCodes.FEATURE_NOT_SUPPORTED -> "FEATURE_NOT_SUPPORTED"
BluetoothStatusCodes.FEATURE_SUPPORTED -> "FEATURE_SUPPORTED"
else -> "ErrorCode: $this"
}
}
val ScanResult.manufacturerDataHead: ByteArray?
get() {
val sparseArray = scanRecord?.manufacturerSpecificData ?: return null
if (sparseArray.size() == 0) return null
return sparseArray.keyAt(0).toShort().toByteArray() + sparseArray.valueAt(0)
}
fun BluetoothGatt.getCharacteristic(
service: String,
characteristic: String,
): BluetoothGattCharacteristic? =
getService(UUID.fromString(service)).getCharacteristic(UUID.fromString(characteristic))
@SuppressLint("MissingPermission")
fun BluetoothGatt.setNotifiable(
gattCharacteristic: BluetoothGattCharacteristic,
bleInputProperty: Long,
) {
val descClientCharConfirmation =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
val descriptor = gattCharacteristic.getDescriptor(descClientCharConfirmation)
val bleInputPropertyEnum: BleInputProperty =
BleInputProperty.values().first { it.value == bleInputProperty }
val (value, enable) = when (bleInputPropertyEnum) {
BleInputProperty.Notification -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE to true
BleInputProperty.Indication -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE to true
else -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE to false
}
descriptor.value = value
setCharacteristicNotification(descriptor.characteristic, enable) && writeDescriptor(descriptor)
}
fun BluetoothDevice.removeBond() {
try {
javaClass.getMethod("removeBond").invoke(this)
} catch (e: Exception) {
Log.e(TAG, "Removing bond has been failed. ${e.message}")
}
}
fun BluetoothGattCharacteristic.getPropertiesList(): ArrayList<Long> {
val propertiesList = arrayListOf<Long>()
if (properties and BluetoothGattCharacteristic.PROPERTY_BROADCAST > 0) {
propertiesList.add(CharacteristicProperty.Broadcast.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_READ > 0) {
propertiesList.add(CharacteristicProperty.Read.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0) {
propertiesList.add(CharacteristicProperty.WriteWithoutResponse.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
propertiesList.add(CharacteristicProperty.Write.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
propertiesList.add(CharacteristicProperty.Notify.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {
propertiesList.add(CharacteristicProperty.Indicate.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE > 0) {
propertiesList.add(CharacteristicProperty.AuthenticatedSignedWrites.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS > 0) {
propertiesList.add(CharacteristicProperty.ExtendedProperties.value)
}
return propertiesList
}
fun Short.toByteArray(byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteArray =
ByteBuffer.allocate(2 /*Short.SIZE_BYTES*/).order(byteOrder).putShort(this).array()
// Errors
fun unknownCharacteristicError(char: String) =
FlutterError("IllegalArgument", "Unknown error", null)
// Future result classes
class BleCharacteristicFuture(
val deviceId: String,
val characteristicId: String,
val serviceId: String,
val result: (Result<ByteArray>) -> Unit,
)
class DiscoverServicesFuture(
val deviceId: String,
val result: (Result<List<UniversalBleService>>) -> Unit,
)
class MtuResultFuture(
val deviceId: String,
val result: (Result<Long>) -> Unit,
)
class WriteResultFuture(
val deviceId: String,
val characteristicId: String,
val serviceId: String,
val result: (Result<Unit>) -> Unit,
)
@@ -0,0 +1,743 @@
package com.navideck.universal_ble
import android.annotation.SuppressLint
import android.app.Activity
import android.bluetooth.*
import android.bluetooth.BluetoothDevice.BOND_BONDED
import android.bluetooth.BluetoothDevice.BOND_NONE
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context.RECEIVER_EXPORTED
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
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.*
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
private const val TAG = "UniversalBlePlugin"
@SuppressLint("MissingPermission")
class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), FlutterPlugin,
ActivityAware, PluginRegistry.ActivityResultListener {
private val bluetoothEnableRequestCode = 2342313
private var callbackChannel: UniversalBleCallbackChannel? = null
private var mainThreadHandler: Handler? = null
private lateinit var context: Context
private var activity: Activity? = null
private lateinit var bluetoothManager: BluetoothManager
private val mtuResultFutureList = mutableListOf<MtuResultFuture>()
private val bleCharacteristicFutureList = mutableListOf<BleCharacteristicFuture>()
private val discoverServicesFutureList = mutableListOf<DiscoverServicesFuture>()
private val writeResultFutureList = mutableListOf<WriteResultFuture>()
private val cachedServicesMap = mutableMapOf<String, List<String>>()
private val devicesStateMap = mutableMapOf<String, Int>()
private var bluetoothEnableRequestFuture: ((Result<Boolean>) -> Unit)? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this)
callbackChannel = UniversalBleCallbackChannel(flutterPluginBinding.binaryMessenger)
context = flutterPluginBinding.applicationContext
mainThreadHandler = Handler(Looper.getMainLooper())
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val intentFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.registerReceiver(broadcastReceiver, intentFilter, RECEIVER_EXPORTED)
} else {
context.registerReceiver(broadcastReceiver, intentFilter)
}
cachedServicesMap.putAll(getCachedServicesMap())
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
context.unregisterReceiver(broadcastReceiver)
callbackChannel = null
mainThreadHandler = null
}
override fun getBluetoothAvailabilityState(callback: (Result<Long>) -> Unit) {
callback(
Result.success(
bluetoothManager.adapter?.state?.toAvailabilityState()
?: AvailabilityState.Unknown.value
)
)
}
override fun enableBluetooth(callback: (Result<Boolean>) -> Unit) {
if (bluetoothManager.adapter.isEnabled) {
callback(Result.success(true))
return
}
if (bluetoothEnableRequestFuture != null) {
callback(
Result.failure(
FlutterError("Failed", "Bluetooth enable request in progress", null)
)
)
return
}
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
activity?.startActivityForResult(enableBtIntent, bluetoothEnableRequestCode)
bluetoothEnableRequestFuture = callback
}
override fun startScan() {
if (!isBluetoothAvailable()) throw FlutterError(
"BluetoothNotEnabled",
"Bluetooth not enabled",
)
bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback)
}
override fun stopScan() {
if (!isBluetoothAvailable()) throw FlutterError(
"BluetoothNotEnabled",
"Bluetooth not enabled",
)
// check if already scanning
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
}
override fun connect(deviceId: String) {
val currentState = devicesStateMap[deviceId]
// If already connected, send connected message,
// if connecting, do nothing
knownGatts.find { it.device.address == deviceId }?.let {
if (currentState == BluetoothGatt.STATE_CONNECTED) {
Log.e(TAG, "$deviceId Already connected")
mainThreadHandler?.post {
callbackChannel?.onConnectionChanged(
deviceId,
BleConnectionState.Connected.value
) {}
}
return
} else if (currentState == BluetoothGatt.STATE_CONNECTING) {
throw FlutterError("Connecting", "Connection already in progress", null)
}
}
val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId)
val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
remoteDevice.connectGatt(
context,
false,
this,
BluetoothDevice.TRANSPORT_LE
)
} else {
remoteDevice.connectGatt(context, false, this)
}
knownGatts.add(gatt)
}
override fun disconnect(deviceId: String) {
// val currentState = devicesStateMap[deviceId]
// If `disconnect` is called before BluetoothGatt.STATE_CONNECTED or BluetoothGatt.STATE_CONNECTING
// there will be no `disconnected` message any more from callback
// if (currentState != null && currentState != BluetoothGatt.STATE_CONNECTED) {
// Log.e(TAG, "$deviceId Already disconnected or disconnecting")
// mainThreadHandler?.post {
// callbackChannel?.onConnectionChanged(
// deviceId,
// BleConnectionState.Disconnected.value
// ) {}
// }
// return
//}
cleanConnection(deviceId.toBluetoothGatt())
}
override fun discoverServices(
deviceId: String,
callback: (Result<List<UniversalBleService>>) -> Unit,
) {
if (!deviceId.toBluetoothGatt().discoverServices()) {
callback(Result.failure(FlutterError("Failed", "Failed to discover services", null)))
return
}
discoverServicesFutureList.add(DiscoverServicesFuture(deviceId, callback))
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
discoverServicesFutureList.filter { it.deviceId == gatt.device.address }.forEach {
discoverServicesFutureList.remove(it)
it.result(
Result.failure(FlutterError("Failed", "Failed to discover services", null))
)
}
return
}
setCachedServices(gatt.device.address, gatt.services.map { it.uuid.toString() })
val universalBleServices = gatt.services.map { service ->
UniversalBleService(
uuid = service.uuid.toString(),
characteristics = service.characteristics.map {
UniversalBleCharacteristic(
uuid = it.uuid.toString(),
properties = it.getPropertiesList()
)
}
)
}
discoverServicesFutureList.filter { it.deviceId == gatt.device.address }.forEach {
discoverServicesFutureList.remove(it)
it.result(Result.success(universalBleServices))
}
}
override fun setNotifiable(
deviceId: String,
service: String,
characteristic: String,
bleInputProperty: Long,
) {
val gatt = deviceId.toBluetoothGatt()
val gattCharacteristic = gatt.getCharacteristic(service, characteristic)
?: throw FlutterError(
"IllegalArgument",
"Unknown characteristic: $characteristic",
null
)
gatt.setNotifiable(gattCharacteristic, bleInputProperty)
}
override fun readValue(
deviceId: String,
service: String,
characteristic: String,
callback: (Result<ByteArray>) -> Unit,
) {
try {
val gatt = deviceId.toBluetoothGatt()
val gattCharacteristic = gatt.getCharacteristic(service, characteristic)
if (gattCharacteristic == null) {
callback(
Result.failure(FlutterError("IllegalArgument", "Unknown characteristic", null))
)
return
}
if (!gatt.readCharacteristic(gattCharacteristic)) {
callback(
Result.failure(unknownCharacteristicError(characteristic))
)
return
}
bleCharacteristicFutureList.add(
BleCharacteristicFuture(
gatt.device.address,
gattCharacteristic.uuid.toString(),
gattCharacteristic.service.uuid.toString(),
callback
)
)
} catch (e: FlutterError) {
callback(Result.failure(e))
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int,
) {
bleCharacteristicFutureList.filter {
it.deviceId == gatt.device.address &&
it.characteristicId == characteristic.uuid.toString() &&
it.serviceId == characteristic.service.uuid.toString()
}.forEach {
bleCharacteristicFutureList.remove(it)
it.result(Result.success(value))
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int,
) {
// Pass data to new api
onCharacteristicRead(gatt, characteristic, characteristic.value, status)
}
override fun writeValue(
deviceId: String,
service: String,
characteristic: String,
value: ByteArray,
bleOutputProperty: Long,
callback: (Result<Unit>) -> Unit,
) {
try {
val gatt = deviceId.toBluetoothGatt()
val gattCharacteristic = gatt.getCharacteristic(service, characteristic)
if (gattCharacteristic == null) {
callback(Result.failure(unknownCharacteristicError(characteristic)))
return
}
var writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
if (bleOutputProperty == BleOutputProperty.withResponse.value) {
if (gattCharacteristic.properties and BluetoothGattCharacteristic.PROPERTY_WRITE == 0) {
callback(
Result.failure(
FlutterError(
"IllegalArgument",
"Characteristic does not support write withResponse",
null
)
)
)
return
}
writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
} else if (bleOutputProperty == BleOutputProperty.withoutResponse.value) {
if (gattCharacteristic.properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE == 0) {
callback(
Result.failure(
FlutterError(
"IllegalArgument",
"Characteristic does not support write withoutResponse",
null
)
)
)
return
}
writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// val result = gatt.writeCharacteristic(gattCharacteristic, value, writeType)
// Log.v(TAG,"writeResult $characteristic: $result => ${result.parseBluetoothStatusCodeError()}")
// return
// }
gattCharacteristic.value = value
gattCharacteristic.writeType = writeType
val result = gatt.writeCharacteristic(gattCharacteristic)
if (!result) {
callback(Result.failure(FlutterError("Failed", "Failed to write", null)))
return
}
if (writeType == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) {
// wait for the result
writeResultFutureList.add(
WriteResultFuture(
gatt.device.address,
gattCharacteristic.uuid.toString(),
gattCharacteristic.service.uuid.toString(),
callback
)
)
} else {
callback(Result.success(Unit))
}
} catch (e: FlutterError) {
callback(Result.failure(e))
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic,
status: Int,
) {
writeResultFutureList.filter {
it.deviceId == gatt?.device?.address &&
it.characteristicId == characteristic.uuid.toString() &&
it.serviceId == characteristic.service.uuid.toString()
}.forEach {
writeResultFutureList.remove(it)
if (status == BluetoothGatt.GATT_SUCCESS) {
it.result(Result.success(Unit))
} else {
it.result(
Result.failure(
FlutterError(
"Failed",
"Failed to write ($status)",
"status $status"
)
)
)
}
}
}
override fun requestMtu(deviceId: String, expectedMtu: Long, callback: (Result<Long>) -> Unit) {
val gatt = deviceId.toBluetoothGatt()
gatt.requestMtu(expectedMtu.toInt())
mtuResultFutureList.add(MtuResultFuture(deviceId, callback))
}
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
val deviceId = gatt?.device?.address ?: return
mtuResultFutureList.filter { it.deviceId == deviceId }.forEach {
mtuResultFutureList.remove(it)
if (status == BluetoothGatt.GATT_SUCCESS) {
it.result(Result.success(mtu.toLong()))
} else {
it.result(Result.failure(FlutterError("Failed to change MTU", null, null)))
}
}
}
override fun isPaired(deviceId: String, callback: (Result<Boolean>) -> Unit) {
val remoteDevice: BluetoothDevice =
bluetoothManager.adapter.getRemoteDevice(deviceId)
callback(Result.success(remoteDevice.bondState == BOND_BONDED))
}
override fun pair(deviceId: String) {
val remoteDevice =
bluetoothManager.adapter.getRemoteDevice(deviceId)
if (remoteDevice.bondState == BOND_NONE) {
if (!remoteDevice.createBond()) {
throw FlutterError("Failed", "Failed to pair", null)
}
} else {
throw FlutterError("AlreadyPair", "Already paired", null)
}
}
override fun unPair(deviceId: String) {
val remoteDevice: BluetoothDevice =
bluetoothManager.adapter.getRemoteDevice(deviceId)
if (remoteDevice.bondState == BOND_BONDED) {
remoteDevice.removeBond()
}
}
override fun getConnectedDevices(
withServices: List<String>,
callback: (Result<List<UniversalBleScanResult>>) -> Unit,
) {
var devices: List<BluetoothDevice> =
bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
if (withServices.isNotEmpty()) {
devices = filterDevicesByServices(devices, withServices)
}
callback(
Result.success(
devices.map {
UniversalBleScanResult(
name = it.name,
deviceId = it.address,
isPaired = it.bondState == BOND_BONDED,
manufacturerDataHead = null,
manufacturerData = null,
rssi = null,
)
}
)
)
}
private fun filterDevicesByServices(
devices: List<BluetoothDevice>,
withServices: List<String>,
): List<BluetoothDevice> {
// If all devices have cached services
if (devices.all { cachedServicesMap[it.address] != null }) {
return devices.filter { device ->
cachedServicesMap[device.address]?.any { uuid -> withServices.contains(uuid) } == true
}
}
// Else discover services off already connected devices
val latch = CountDownLatch(devices.size)
val resultMap = mutableMapOf<String, Boolean>()
devices.forEach { device ->
discoverServicesOffAlreadyConnectedDevice(device) { uuids ->
resultMap[device.address] =
uuids?.any { uuid -> withServices.contains(uuid) } == true
latch.countDown()
}
}
try {
val timeout = (devices.size * 2).toLong()
latch.await(timeout, TimeUnit.SECONDS)
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
return devices.filter { resultMap[it.address] == true }
}
private fun discoverServicesOffAlreadyConnectedDevice(
device: BluetoothDevice,
callback: (List<String>?) -> Unit,
) {
// Check if already cached
cachedServicesMap[device.address]?.let {
callback(it)
return
}
// To avoid duplicate callback
var isUpdated = false
fun updateCallback(uuids: List<String>?) {
if (isUpdated) return
callback(uuids)
isUpdated = true
}
// If its a known gatt, just discover services
knownGatts.find { it.device.address == device.address }?.let {
it.services?.let { services ->
updateCallback(services.map { service -> service.uuid.toString() })
return
}
if (it.discoverServices()) {
discoverServicesFutureList.add(
DiscoverServicesFuture(device.address) { uuids: Result<List<UniversalBleService>> ->
if (uuids.isSuccess) {
updateCallback(uuids.getOrNull()?.map { it.uuid })
} else {
updateCallback(null)
}
}
)
return
}
}
// Else connect to Gatt, discover services, then disconnect
val callbackHandler = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
if (gatt?.discoverServices() != true) {
updateCallback(null)
if (!knownGatts.any { it.device.address == device.address }) {
gatt?.disconnect()
}
}
} else {
updateCallback(null)
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val uuids = gatt?.services?.map { it.uuid.toString() }
if (uuids != null) {
setCachedServices(device.address, uuids)
}
updateCallback(uuids)
}
if (!knownGatts.any { it.device.address == device.address }) {
gatt?.disconnect()
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
device.connectGatt(context, false, callbackHandler, BluetoothDevice.TRANSPORT_LE)
} else {
device.connectGatt(context, false, callbackHandler)
}
}
private fun cleanConnection(gatt: BluetoothGatt) {
knownGatts.remove(gatt)
gatt.disconnect()
bleCharacteristicFutureList.removeAll {
it.deviceId == gatt.device.address
}
mtuResultFutureList.removeAll {
it.deviceId == gatt.device.address
}
discoverServicesFutureList.removeAll {
it.deviceId == gatt.device.address
}
}
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
mainThreadHandler?.post {
callbackChannel?.onAvailabilityChanged(
bluetoothManager.adapter?.state?.toAvailabilityState()
?: AvailabilityState.Unknown.value
) {}
}
} else if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val device: BluetoothDevice =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) ?: return
// get pairing failed error
when (intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)) {
BluetoothDevice.BOND_BONDING -> {
Log.v(TAG, "${device.address} BOND_BONDING")
}
BluetoothDevice.ERROR -> {
mainThreadHandler?.post {
callbackChannel?.onPairStateChange(
device.address,
false,
"No pairing state received"
) {}
}
}
BOND_BONDED -> {
mainThreadHandler?.post {
callbackChannel?.onPairStateChange(
device.address,
true,
null
) {}
}
}
BOND_NONE -> {
mainThreadHandler?.post {
callbackChannel?.onPairStateChange(
device.address,
false,
null
) {}
}
}
}
}
}
}
private val scanCallback = object : ScanCallback() {
override fun onScanFailed(errorCode: Int) {
Log.e(TAG, "OnScanFailed: ${errorCode.parseScanErrorMessage()}")
}
override fun onScanResult(callbackType: Int, result: ScanResult) {
// Log.v(TAG, "onScanResult: $result")
mainThreadHandler?.post {
callbackChannel?.onScanResult(
UniversalBleScanResult(
name = result.device.name,
deviceId = result.device.address,
isPaired = result.device.bondState == BOND_BONDED,
manufacturerDataHead = result.manufacturerDataHead,
rssi = result.rssi.toLong()
)
) {}
}
}
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
Log.v(TAG, "onBatchScanResults: $results")
}
}
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
devicesStateMap[gatt.device.address] = newState
if (newState == BluetoothGatt.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
mainThreadHandler?.post {
callbackChannel?.onConnectionChanged(
gatt.device.address,
BleConnectionState.Connected.value
) {}
}
} else {
cleanConnection(gatt)
mainThreadHandler?.post {
callbackChannel?.onConnectionChanged(
gatt.device.address,
BleConnectionState.Disconnected.value
) {}
}
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
) {
mainThreadHandler?.post {
callbackChannel?.onValueChanged(
deviceIdArg = gatt.device.address,
characteristicIdArg = characteristic.uuid.toString(),
valueArg = characteristic.value
) {}
}
}
private fun isBluetoothAvailable(): Boolean {
return bluetoothManager.adapter.isEnabled
}
private fun setCachedServices(deviceId: String, services: List<String>) {
val cachedServicesSharedPref = context.getSharedPreferences(
"com.navideck.universal_ble.services",
Context.MODE_PRIVATE
)
cachedServicesSharedPref.edit().putStringSet(deviceId, services.toSet()).apply()
cachedServicesMap[deviceId] = services
}
private fun getCachedServicesMap(): Map<String, List<String>> {
val cachedServicesSharedPref = context.getSharedPreferences(
"com.navideck.universal_ble.services",
Context.MODE_PRIVATE
)
return cachedServicesSharedPref.all.mapValues { (_, value) ->
(value as? Set<*>)?.map { it.toString() } ?: emptyList()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode == bluetoothEnableRequestCode) {
val future = bluetoothEnableRequestFuture ?: return false
future(Result.success(resultCode == Activity.RESULT_OK))
bluetoothEnableRequestFuture = null
return true
}
return false
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromActivityForConfigChanges() {}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}
}
@@ -0,0 +1,27 @@
package com.navideck.universal_ble
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import org.mockito.Mockito
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class UniversalBlePluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = UniversalBlePlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}
+38
View File
@@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh
View File
+525
View File
@@ -0,0 +1,525 @@
// Autogenerated from Pigeon (v13.0.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)"
]
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleScanResult {
var deviceId: String
var name: String? = nil
var isPaired: Bool? = nil
var rssi: Int64? = nil
var manufacturerData: FlutterStandardTypedData? = nil
var manufacturerDataHead: FlutterStandardTypedData? = nil
static func fromList(_ list: [Any?]) -> UniversalBleScanResult? {
let deviceId = list[0] as! String
let name: String? = nilOrValue(list[1])
let isPaired: Bool? = nilOrValue(list[2])
let rssi: Int64? = isNullish(list[3]) ? nil : (list[3] is Int64? ? list[3] as! Int64? : Int64(list[3] as! Int32))
let manufacturerData: FlutterStandardTypedData? = nilOrValue(list[4])
let manufacturerDataHead: FlutterStandardTypedData? = nilOrValue(list[5])
return UniversalBleScanResult(
deviceId: deviceId,
name: name,
isPaired: isPaired,
rssi: rssi,
manufacturerData: manufacturerData,
manufacturerDataHead: manufacturerDataHead
)
}
func toList() -> [Any?] {
return [
deviceId,
name,
isPaired,
rssi,
manufacturerData,
manufacturerDataHead,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleService {
var uuid: String
var characteristics: [UniversalBleCharacteristic?]? = nil
static func fromList(_ list: [Any?]) -> UniversalBleService? {
let uuid = list[0] as! String
let characteristics: [UniversalBleCharacteristic?]? = nilOrValue(list[1])
return UniversalBleService(
uuid: uuid,
characteristics: characteristics
)
}
func toList() -> [Any?] {
return [
uuid,
characteristics,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleCharacteristic {
var uuid: String
var properties: [Int64?]
static func fromList(_ list: [Any?]) -> UniversalBleCharacteristic? {
let uuid = list[0] as! String
let properties = list[1] as! [Int64?]
return UniversalBleCharacteristic(
uuid: uuid,
properties: properties
)
}
func toList() -> [Any?] {
return [
uuid,
properties,
]
}
}
private class UniversalBlePlatformChannelCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 128:
return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?])
case 129:
return UniversalBleScanResult.fromList(self.readValue() as! [Any?])
case 130:
return UniversalBleService.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class UniversalBlePlatformChannelCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? UniversalBleCharacteristic {
super.writeByte(128)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleScanResult {
super.writeByte(129)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleService {
super.writeByte(130)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class UniversalBlePlatformChannelCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return UniversalBlePlatformChannelCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return UniversalBlePlatformChannelCodecWriter(data: data)
}
}
class UniversalBlePlatformChannelCodec: FlutterStandardMessageCodec {
static let shared = UniversalBlePlatformChannelCodec(readerWriter: UniversalBlePlatformChannelCodecReaderWriter())
}
/// Flutter -> Native
///
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol UniversalBlePlatformChannel {
func getBluetoothAvailabilityState(completion: @escaping (Result<Int64, Error>) -> Void)
func enableBluetooth(completion: @escaping (Result<Bool, Error>) -> Void)
func startScan() throws
func stopScan() throws
func connect(deviceId: String) throws
func disconnect(deviceId: String) throws
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64) throws
func discoverServices(deviceId: String, completion: @escaping (Result<[UniversalBleService], Error>) -> Void)
func readValue(deviceId: String, service: String, characteristic: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void)
func requestMtu(deviceId: String, expectedMtu: Int64, completion: @escaping (Result<Int64, Error>) -> Void)
func writeValue(deviceId: String, service: String, characteristic: String, value: FlutterStandardTypedData, bleOutputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
func isPaired(deviceId: String, completion: @escaping (Result<Bool, Error>) -> Void)
func pair(deviceId: String) throws
func unPair(deviceId: String) throws
func getConnectedDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class UniversalBlePlatformChannelSetup {
/// The codec used by UniversalBlePlatformChannel.
static var codec: FlutterStandardMessageCodec { UniversalBlePlatformChannelCodec.shared }
/// Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UniversalBlePlatformChannel?) {
let getBluetoothAvailabilityStateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getBluetoothAvailabilityStateChannel.setMessageHandler { _, reply in
api.getBluetoothAvailabilityState() { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getBluetoothAvailabilityStateChannel.setMessageHandler(nil)
}
let enableBluetoothChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
enableBluetoothChannel.setMessageHandler { _, reply in
api.enableBluetooth() { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
enableBluetoothChannel.setMessageHandler(nil)
}
let startScanChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
startScanChannel.setMessageHandler { _, reply in
do {
try api.startScan()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
startScanChannel.setMessageHandler(nil)
}
let stopScanChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
stopScanChannel.setMessageHandler { _, reply in
do {
try api.stopScan()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
stopScanChannel.setMessageHandler(nil)
}
let connectChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
connectChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.connect(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
connectChannel.setMessageHandler(nil)
}
let disconnectChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
disconnectChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.disconnect(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
disconnectChannel.setMessageHandler(nil)
}
let setNotifiableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setNotifiableChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let serviceArg = args[1] as! String
let characteristicArg = args[2] as! String
let bleInputPropertyArg = args[3] is Int64 ? args[3] as! Int64 : Int64(args[3] as! Int32)
do {
try api.setNotifiable(deviceId: deviceIdArg, service: serviceArg, characteristic: characteristicArg, bleInputProperty: bleInputPropertyArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
setNotifiableChannel.setMessageHandler(nil)
}
let discoverServicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
discoverServicesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
api.discoverServices(deviceId: deviceIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
discoverServicesChannel.setMessageHandler(nil)
}
let readValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
readValueChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let serviceArg = args[1] as! String
let characteristicArg = args[2] as! String
api.readValue(deviceId: deviceIdArg, service: serviceArg, characteristic: characteristicArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
readValueChannel.setMessageHandler(nil)
}
let requestMtuChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestMtuChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let expectedMtuArg = args[1] is Int64 ? args[1] as! Int64 : Int64(args[1] as! Int32)
api.requestMtu(deviceId: deviceIdArg, expectedMtu: expectedMtuArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestMtuChannel.setMessageHandler(nil)
}
let writeValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
writeValueChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let serviceArg = args[1] as! String
let characteristicArg = args[2] as! String
let valueArg = args[3] as! FlutterStandardTypedData
let bleOutputPropertyArg = args[4] is Int64 ? args[4] as! Int64 : Int64(args[4] as! Int32)
api.writeValue(deviceId: deviceIdArg, service: serviceArg, characteristic: characteristicArg, value: valueArg, bleOutputProperty: bleOutputPropertyArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
writeValueChannel.setMessageHandler(nil)
}
let isPairedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
isPairedChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
api.isPaired(deviceId: deviceIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
isPairedChannel.setMessageHandler(nil)
}
let pairChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
pairChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.pair(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
pairChannel.setMessageHandler(nil)
}
let unPairChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
unPairChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.unPair(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
unPairChannel.setMessageHandler(nil)
}
let getConnectedDevicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getConnectedDevicesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let withServicesArg = args[0] as! [String]
api.getConnectedDevices(withServices: withServicesArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getConnectedDevicesChannel.setMessageHandler(nil)
}
}
}
private class UniversalBleCallbackChannelCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 128:
return UniversalBleScanResult.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class UniversalBleCallbackChannelCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? UniversalBleScanResult {
super.writeByte(128)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class UniversalBleCallbackChannelCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return UniversalBleCallbackChannelCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return UniversalBleCallbackChannelCodecWriter(data: data)
}
}
class UniversalBleCallbackChannelCodec: FlutterStandardMessageCodec {
static let shared = UniversalBleCallbackChannelCodec(readerWriter: UniversalBleCallbackChannelCodecReaderWriter())
}
/// Native -> Flutter
///
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol UniversalBleCallbackChannelProtocol {
func onAvailabilityChanged(state stateArg: Int64, completion: @escaping (Result<Void, FlutterError>) -> Void)
func onPairStateChange(deviceId deviceIdArg: String, isPaired isPairedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, FlutterError>) -> Void)
func onScanResult(result resultArg: UniversalBleScanResult, completion: @escaping (Result<Void, FlutterError>) -> Void)
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, completion: @escaping (Result<Void, FlutterError>) -> Void)
func onConnectionChanged(deviceId deviceIdArg: String, state stateArg: Int64, completion: @escaping (Result<Void, FlutterError>) -> Void)
}
class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol {
private let binaryMessenger: FlutterBinaryMessenger
init(binaryMessenger: FlutterBinaryMessenger){
self.binaryMessenger = binaryMessenger
}
var codec: FlutterStandardMessageCodec {
return UniversalBleCallbackChannelCodec.shared
}
func onAvailabilityChanged(state stateArg: Int64, completion: @escaping (Result<Void, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged", binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([stateArg] as [Any?]) { _ in
completion(.success(Void()))
}
}
func onPairStateChange(deviceId deviceIdArg: String, isPaired isPairedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange", binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, isPairedArg, errorArg] as [Any?]) { _ in
completion(.success(Void()))
}
}
func onScanResult(result resultArg: UniversalBleScanResult, completion: @escaping (Result<Void, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult", binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([resultArg] as [Any?]) { _ in
completion(.success(Void()))
}
}
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, completion: @escaping (Result<Void, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged", binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, characteristicIdArg, valueArg] as [Any?]) { _ in
completion(.success(Void()))
}
}
func onConnectionChanged(deviceId deviceIdArg: String, state stateArg: Int64, completion: @escaping (Result<Void, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged", binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, stateArg] as [Any?]) { _ in
completion(.success(Void()))
}
}
}
+172
View File
@@ -0,0 +1,172 @@
//
// UniversalBleHelper.swift
// universal_ble
//
// Created by Rohit Sangwan on 25/10/23.
//
import CoreBluetooth
import Foundation
#if os(iOS)
import Flutter
#elseif os(OSX)
import FlutterMacOS
#endif
enum BleInputProperty: Int {
case disabled = 0
case notification = 1
case indication = 2
}
enum BleOutputProperty: Int {
case withResponse = 0
case withoutResponse = 1
}
enum BlueConnectionState: Int64 {
case connected = 0
case disconnected = 1
}
enum AvailabilityState: Int64 {
case unknown = 0
case resetting = 1
case unsupported = 2
case unauthorized = 3
case poweredOff = 4
case poweredOn = 5
}
enum CharacteristicProperty: Int64 {
case broadcast = 0
case read = 1
case writeWithoutResponse = 2
case write = 3
case notify = 4
case indicate = 5
case authenticatedSignedWrites = 6
case extendedProperties = 7
}
extension CBCharacteristicProperties {
var toCharacteristicProperty: [Int64] {
var properties = [Int64]()
if contains(.broadcast) {
properties.append(CharacteristicProperty.broadcast.rawValue)
}
if contains(.read) {
properties.append(CharacteristicProperty.read.rawValue)
}
if contains(.writeWithoutResponse) {
properties.append(CharacteristicProperty.writeWithoutResponse.rawValue)
}
if contains(.write) {
properties.append(CharacteristicProperty.write.rawValue)
}
if contains(.notify) {
properties.append(CharacteristicProperty.notify.rawValue)
}
if contains(.indicate) {
properties.append(CharacteristicProperty.indicate.rawValue)
}
if contains(.authenticatedSignedWrites) {
properties.append(CharacteristicProperty.authenticatedSignedWrites.rawValue)
}
if contains(.extendedProperties) {
properties.append(CharacteristicProperty.extendedProperties.rawValue)
}
return properties
}
}
extension CBManagerState {
func toAvailabilityState() -> AvailabilityState {
switch self {
case .unknown:
return AvailabilityState.unknown
case .resetting:
return AvailabilityState.resetting
case .unsupported:
return AvailabilityState.unsupported
case .unauthorized:
return AvailabilityState.unauthorized
case .poweredOff:
return AvailabilityState.poweredOff
case .poweredOn:
return AvailabilityState.poweredOn
@unknown default:
return AvailabilityState.unknown
}
}
}
public extension CBUUID {
var uuidStr: String {
uuidString.lowercased()
}
}
public extension CBPeripheral {
// FIXME: https://forums.developer.apple.com/thread/84375
var uuid: UUID {
value(forKey: "identifier") as! NSUUID as UUID
}
func getCharacteristic(_ characteristic: String, of service: String) -> CBCharacteristic? {
let GSS_SUFFIX = "0000-1000-8000-00805f9b34fb"
let s = services?.first {
$0.uuid.uuidStr.lowercased() == service.lowercased() || service.lowercased() == "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)".lowercased()
}
let c = s?.characteristics?.first {
$0.uuid.uuidStr.lowercased() == characteristic.lowercased() || characteristic.lowercased() == "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)".lowercased()
}
return c
}
func setNotifiable(_ bleInputProperty: String, for characteristic: String, of service: String) {
guard let characteristic = getCharacteristic(characteristic, of: service) else {
return
}
setNotifyValue(bleInputProperty != "disabled", for: characteristic)
}
}
// Future classes
class CharacteristicReadFuture {
let deviceId: String
let characteristicId: String
let serviceId: String?
let result: (Result<FlutterStandardTypedData, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
self.deviceId = deviceId
self.characteristicId = characteristicId
self.serviceId = serviceId
self.result = result
}
}
class CharacteristicWriteFuture {
let deviceId: String
let characteristicId: String
let serviceId: String?
let result: (Result<Void, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<Void, Error>) -> Void) {
self.deviceId = deviceId
self.characteristicId = characteristicId
self.serviceId = serviceId
self.result = result
}
}
class DiscoverServicesFuture {
let deviceId: String
let result: (Result<[UniversalBleService], Error>) -> Void
init(deviceId: String, result: @escaping (Result<[UniversalBleService], Error>) -> Void) {
self.deviceId = deviceId
self.result = result
}
}
+334
View File
@@ -0,0 +1,334 @@
import CoreBluetooth
#if os(iOS)
import Flutter
import UIKit
#elseif os(OSX)
import Cocoa
import FlutterMacOS
#endif
public class UniversalBlePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
var messenger: FlutterBinaryMessenger
#if os(iOS)
messenger = registrar.messenger()
#elseif os(macOS)
messenger = registrar.messenger
#endif
let callbackChannel = UniversalBleCallbackChannel(binaryMessenger: messenger)
let api = BleCentralDarwin(callbackChannel: callbackChannel)
UniversalBlePlatformChannelSetup.setUp(binaryMessenger: messenger, api: api)
}
}
private var discoveredPeripherals = [String: CBPeripheral]()
private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentralManagerDelegate, CBPeripheralDelegate {
var callbackChannel: UniversalBleCallbackChannel
private lazy var manager: CBCentralManager = .init(delegate: self, queue: nil)
private var discoveredServicesProgressMap: [String: [UniversalBleService]] = [:]
private var characteristicReadFutures = [CharacteristicReadFuture]()
private var characteristicWriteFutures = [CharacteristicWriteFuture]()
private var discoverServicesFutures = [DiscoverServicesFuture]()
private var bluetoothAvailabilityStateCallback: ((Result<Int64, Error>) -> Void)? {
didSet {
oldValue?(Result.success(AvailabilityState.unknown.rawValue))
}
}
init(callbackChannel: UniversalBleCallbackChannel) {
self.callbackChannel = callbackChannel
super.init()
}
func getBluetoothAvailabilityState(completion: @escaping (Result<Int64, Error>) -> Void) {
let managerState = manager.state
if managerState == .unknown {
bluetoothAvailabilityStateCallback = completion
} else {
completion(.success(managerState.toAvailabilityState().rawValue))
}
}
func enableBluetooth(completion: @escaping (Result<Bool, Error>) -> Void) {
completion(Result.failure(FlutterError(code: "NotSupported", message: nil, details: nil)))
}
func startScan() throws {
manager.scanForPeripherals(withServices: nil)
}
func stopScan() throws {
manager.stopScan()
}
func connect(deviceId: String) throws {
let peripheral = try deviceId.getPeripheral()
peripheral.delegate = self
manager.connect(peripheral)
}
func disconnect(deviceId: String) throws {
let peripheral = try deviceId.getPeripheral()
if peripheral.state != CBPeripheralState.disconnected {
manager.cancelPeripheralConnection(peripheral)
}
cleanUpConnection(deviceId: deviceId)
}
func cleanUpConnection(deviceId: String) {
characteristicReadFutures.removeAll { future in
future.deviceId == deviceId
}
discoverServicesFutures.removeAll { future in
future.deviceId == deviceId
}
discoveredServicesProgressMap[deviceId] = nil
}
func discoverServices(deviceId: String, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) {
guard let peripheral = discoveredPeripherals[deviceId] else {
completion(Result.failure(
FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
if discoveredServicesProgressMap[deviceId] != nil {
completion(Result.failure(FlutterError(code: "AlreadyInProgress", message: "Services discovery already in progress for :\(deviceId)", details: nil)))
return
}
peripheral.discoverServices(nil)
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
}
private func onServicesDiscovered(deviceId: String, services: [UniversalBleService]) {
discoverServicesFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(Result.success(services))
return true
}
return false
}
}
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64) throws {
let peripheral = try deviceId.getPeripheral()
guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
throw FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil)
}
if bleInputProperty == BleInputProperty.notification.rawValue && !c.properties.contains(.notify) {
throw FlutterError(code: "InvalidAction", message: "Characteristic does not support notify", details: nil)
}
if bleInputProperty == BleInputProperty.indication.rawValue && !c.properties.contains(.indicate) {
throw FlutterError(code: "InvalidAction", message: "Characteristic does not support indicate", details: nil)
}
let shouldNotify = bleInputProperty != BleInputProperty.disabled.rawValue
peripheral.setNotifyValue(shouldNotify, for: c)
}
func readValue(deviceId: String, service: String, characteristic: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
guard let peripheral = discoveredPeripherals[deviceId] else {
completion(Result.failure(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
guard let gattCharacteristic = peripheral.getCharacteristic(characteristic, of: service) else {
completion(Result.failure(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil)))
return
}
if !gattCharacteristic.properties.contains(.read) {
completion(Result.failure(FlutterError(code: "InvalidAction", message: "Characteristic does not support read", details: nil)))
return
}
peripheral.readValue(for: gattCharacteristic)
characteristicReadFutures.append(CharacteristicReadFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion))
}
func writeValue(deviceId: String, service: String, characteristic: String, value: FlutterStandardTypedData, bleOutputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
guard let peripheral = discoveredPeripherals[deviceId] else {
completion(Result.failure(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
guard let gattCharacteristic = peripheral.getCharacteristic(characteristic, of: service) else {
completion(Result.failure(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil)))
return
}
let type = bleOutputProperty == BleOutputProperty.withoutResponse.rawValue ? CBCharacteristicWriteType.withoutResponse : CBCharacteristicWriteType.withResponse
if type == CBCharacteristicWriteType.withResponse {
if !gattCharacteristic.properties.contains(.write) {
completion(Result.failure(FlutterError(code: "InvalidAction", message: "Characteristic does not support write withResponse", details: nil)))
return
}
} else if type == CBCharacteristicWriteType.withoutResponse {
if !gattCharacteristic.properties.contains(.writeWithoutResponse) {
completion(Result.failure(FlutterError(code: "InvalidAction", message: "Characteristic does not support write withoutResponse", details: nil)))
return
}
}
peripheral.writeValue(value.data, for: gattCharacteristic, type: type)
if type == CBCharacteristicWriteType.withResponse {
// Wait for future response
characteristicWriteFutures.append(CharacteristicWriteFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion))
} else {
completion(Result.success({}()))
}
}
func requestMtu(deviceId: String, expectedMtu _: Int64, completion: @escaping (Result<Int64, Error>) -> Void) {
guard let peripheral = discoveredPeripherals[deviceId] else {
completion(Result.failure(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
let mtu = peripheral.maximumWriteValueLength(for: CBCharacteristicWriteType.withoutResponse)
let GATT_HEADER_LENGTH = 3
let mtuResult = Int64(mtu + GATT_HEADER_LENGTH)
completion(Result.success(mtuResult))
}
func isPaired(deviceId _: String, completion: @escaping (Result<Bool, Error>) -> Void) {
completion(Result.failure(FlutterError(code: "NotSupported", message: nil, details: nil)))
}
func pair(deviceId _: String) throws {
throw FlutterError(code: "NotSupported", message: nil, details: nil)
}
func unPair(deviceId _: String) throws {
throw FlutterError(code: "NotSupported", message: nil, details: nil)
}
func getConnectedDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void) {
var filterCBUUID = withServices.map { CBUUID(string: $0) }
// We can't keep this filter empty, so adding a default filter
if filterCBUUID.isEmpty { filterCBUUID.append(CBUUID(string: "1800")) }
let bleDevices = manager.retrieveConnectedPeripherals(withServices: filterCBUUID)
bleDevices.forEach { discoveredPeripherals[$0.uuid.uuidString] = $0 }
completion(Result.success(bleDevices.map {
UniversalBleScanResult(
deviceId: $0.uuid.uuidString,
name: $0.name ?? ""
)
}))
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let state = central.state.toAvailabilityState().rawValue
callbackChannel.onAvailabilityChanged(state: state) { _ in }
bluetoothAvailabilityStateCallback?(Result.success(state))
}
public func centralManager(_: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
discoveredPeripherals[peripheral.uuid.uuidString] = peripheral
let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
callbackChannel.onScanResult(result: UniversalBleScanResult(
deviceId: peripheral.uuid.uuidString,
name: peripheral.name,
isPaired: nil,
rssi: RSSI as? Int64,
manufacturerData: FlutterStandardTypedData(bytes: manufacturerData ?? Data())
)) { _ in }
}
public func centralManager(_: CBCentralManager, didConnect peripheral: CBPeripheral) {
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, state: BlueConnectionState.connected.rawValue) { _ in }
}
public func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error _: Error?) {
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, state: BlueConnectionState.disconnected.rawValue) { _ in }
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
let deviceId = peripheral.identifier.uuidString
guard let services = peripheral.services else {
onServicesDiscovered(deviceId: deviceId, services: [])
return
}
discoveredServicesProgressMap[deviceId] = services.map { UniversalBleService(uuid: $0.uuid.uuidString, characteristics: nil) }
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?) {
let deviceId = peripheral.identifier.uuidString
var universalBleCharacteristicsList: [UniversalBleCharacteristic] = []
for characteristic in service.characteristics ?? [] {
universalBleCharacteristicsList.append(
UniversalBleCharacteristic(uuid: characteristic.uuid.uuidString, properties: characteristic.properties.toCharacteristicProperty))
}
// Update discoveredServicesProgressMap
if let index = discoveredServicesProgressMap[deviceId]?.firstIndex(where: { $0.uuid == service.uuid.uuidString }) {
discoveredServicesProgressMap[deviceId]?[index] = UniversalBleService(uuid: service.uuid.uuidString, characteristics: universalBleCharacteristicsList)
}
// Check if all services and their characteristics have been discovered
if discoveredServicesProgressMap[deviceId]?.allSatisfy({ $0.characteristics != nil }) ?? false {
onServicesDiscovered(deviceId: deviceId, services: discoveredServicesProgressMap[deviceId] ?? [])
discoveredServicesProgressMap[deviceId] = nil
}
}
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("peripheral:didWriteValueForCharacteristic \(characteristic.uuid.uuidStr) error: \(String(describing: error))")
// Update futures for writeValue
characteristicWriteFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if error != nil {
future.result(Result.failure(FlutterError(code: "WriteFailed", message: String(describing: error), details: nil)))
} else {
future.result(Result.success({}()))
}
return true
}
return false
}
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// Update callbackChannel if notifying
if characteristic.isNotifying {
if let characteristicValue = characteristic.value {
callbackChannel.onValueChanged(deviceId: peripheral.uuid.uuidString, characteristicId: characteristic.uuid.uuidStr, value: FlutterStandardTypedData(bytes: characteristicValue)) { _ in }
}
}
if characteristicReadFutures.count == 0 {
return
}
// Update futures for readValue
characteristicReadFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if error != nil {
future.result(Result.failure(FlutterError(code: "ReadFailed", message: String(describing: error), details: nil)))
} else {
if let characteristicValue = characteristic.value {
future.result(Result.success(FlutterStandardTypedData(bytes: characteristicValue)))
} else {
future.result(Result.failure(FlutterError(code: "ReadFailed", message: "No value", details: nil)))
}
}
return true
}
return false
}
}
}
extension String {
func getPeripheral() throws -> CBPeripheral {
guard let peripheral = discoveredPeripherals[self] else {
throw FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)
}
return peripheral
}
}
extension FlutterError: Error {}
+25
View File
@@ -0,0 +1,25 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint universal_ble.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'universal_ble'
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.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'
s.ios.deployment_target = '9.0'
s.osx.deployment_target = '10.12'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end
+44
View File
@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+30
View File
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ead455963c12b453cdb2358cad34969c76daf180"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
- platform: web
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+16
View File
@@ -0,0 +1,16 @@
# universal_ble_example
Demonstrates how to use the universal_ble 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://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
+28
View File
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+13
View File
@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
+67
View File
@@ -0,0 +1,67 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.navideck.universal_ble_example"
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.navideck.universal_ble_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
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 {}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool 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>
@@ -0,0 +1,38 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
<application
android:label="universal_ble_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
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">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<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>
@@ -0,0 +1,6 @@
package com.navideck.universal_ble_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
@@ -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:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -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

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool 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>
+31
View File
@@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
@@ -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-7.5-all.zip
+20
View File
@@ -0,0 +1,20 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
include ":app"
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
+34
View File
@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -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>en</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>11.0</string>
</dict>
</plist>
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
+2
View File
@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+44
View File
@@ -0,0 +1,44 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.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 flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
+41
View File
@@ -0,0 +1,41 @@
PODS:
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- integration_test (0.0.1):
- Flutter
- permission_handler_apple (9.1.1):
- Flutter
- universal_ble (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
universal_ble:
:path: ".symlinks/plugins/universal_ble/darwin"
SPEC CHECKSUMS:
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
integration_test: 13825b8a9334a850581300559b8839134b124670
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
COCOAPODS: 1.14.3
@@ -0,0 +1,724 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3C020DDC565FF8D37F3235C5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E92536821956EA00892DD639 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
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 */; };
B2742FE9E6C1C51A78D26865 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
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>"; };
30B9BF32CD25D1ECB8BB5DCA /* 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>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
4414252E2351EBDB7761F83A /* 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>"; };
6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/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>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A11EED44BE0324AD23055698 /* 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>"; };
E92536821956EA00892DD639 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8EE4584064C335A1F66BB6D9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B2742FE9E6C1C51A78D26865 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3C020DDC565FF8D37F3235C5 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
68033A1F47BF85C19A1ED513 /* Frameworks */ = {
isa = PBXGroup;
children = (
E92536821956EA00892DD639 /* Pods_Runner.framework */,
9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
FAF8A3758B0770B81319B3AE /* Pods */,
68033A1F47BF85C19A1ED513 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
FAF8A3758B0770B81319B3AE /* Pods */ = {
isa = PBXGroup;
children = (
30B9BF32CD25D1ECB8BB5DCA /* Pods-Runner.debug.xcconfig */,
4414252E2351EBDB7761F83A /* Pods-Runner.release.xcconfig */,
A11EED44BE0324AD23055698 /* Pods-Runner.profile.xcconfig */,
411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */,
0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */,
6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
7E79F47357B64513A488AC3E /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
8EE4584064C335A1F66BB6D9 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
3CE2E408302E9BF272A3BE07 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
2F5EDC3CC74E7431E4CB95DF /* [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 = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
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 */
2F5EDC3CC74E7431E4CB95DF /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
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;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
3CE2E408302E9BF272A3BE07 /* [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;
};
7E79F47357B64513A488AC3E /* [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-RunnerTests-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;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
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";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency 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;
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 = 11.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;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = QQAZR43YM7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
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 = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
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 = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = QQAZR43YM7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = QQAZR43YM7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -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>
@@ -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>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
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>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</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>
+10
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>
@@ -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>
@@ -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>PreviewsEnabled</key>
<false/>
</dict>
</plist>
+13
View File
@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -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: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -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

@@ -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.
@@ -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>
@@ -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>
+53
View File
@@ -0,0 +1,53 @@
<?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>CFBundleDisplayName</key>
<string>Universal Ble</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>universal_ble_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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>example</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>example</string>
</dict>
</plist>
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
+26
View File
@@ -0,0 +1,26 @@
import Flutter
import UIKit
import XCTest
@testable import universal_ble
// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
//
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
class RunnerTests: XCTestCase {
func testGetPlatformVersion() {
let plugin = UniversalBlePlugin()
let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
let resultExpectation = expectation(description: "result block must be called.")
plugin.handle(call) { result in
XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
resultExpectation.fulfill()
}
waitForExpectations(timeout: 1)
}
}
+303
View File
@@ -0,0 +1,303 @@
// ignore_for_file: use_build_context_synchronously, avoid_print
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/peripheral_detail_page.dart';
import 'package:universal_ble_example/permission_validator.dart';
// Run with auto connect to : auto Scan , FindDevice, Connect, DiscoverServices
const autoConnect = bool.fromEnvironment('AUTO_CONNECT', defaultValue: false);
const deviceIdToConnect = "50:e9:f1:c2:5e:2e";
const deviceNameFilterForAutoConnect = "MyDevice";
const primaryColor = Colors.blue;
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<BleScanResult>? _subscription;
final _scanResults = <BleScanResult>[];
AvailabilityState? bleAvailabilityState;
List<String> services = [
"00001800-0000-1000-8000-00805f9b34fb",
"00002a00-0000-1000-8000-00805f9b34fb",
"00002a01-0000-1000-8000-00805f9b34fb",
"00002a19-0000-1000-8000-00805f9b34fb",
"8000cc00-cc00-ffff-ffff-ffffffffffff",
"8000dd00-dd00-ffff-ffff-ffffffffffff",
];
// Set the web request options with the required services
get requestOptions =>
WebRequestOptionsBuilder.acceptAllDevices(optionalServices: services);
void initialize() {
UniversalBle.getBluetoothAvailabilityState().then((value) {
print("GetBluetoothAvailabilityState: ${value.name}");
setState(() {
bleAvailabilityState = value;
});
});
UniversalBle.onAvailabilityChange = (state) {
print("OnAvailabilityChange: ${state.name}");
setState(() {
bleAvailabilityState = state;
});
};
if (autoConnect) {
UniversalBle.startScan(webRequestOptions: requestOptions);
}
UniversalBle.onScanResult = (result) {
if (!_scanResults.any((r) => r.deviceId == result.deviceId)) {
setState(() => _scanResults.add(result));
} else {
setState(() {
int index = _scanResults
.indexWhere((element) => element.deviceId == result.deviceId);
if (result.name == null && _scanResults[index].name != null) {
result.name = _scanResults[index].name;
}
_scanResults[index] = result;
});
}
if (autoConnect &&
(result.deviceId == deviceIdToConnect ||
(result.name?.contains(deviceNameFilterForAutoConnect) ==
true))) {
print("Auto connecting to ${result.deviceId}");
UniversalBle.stopScan();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PeripheralDetailPage(result.deviceId, true),
));
}
};
}
@override
void initState() {
super.initState();
initialize();
}
@override
void dispose() {
super.dispose();
_subscription?.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Universal Ble'),
),
body: Column(
children: [
_buildButtons(),
const Divider(
color: Colors.blue,
),
_scanResults.isEmpty
? const Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.bluetooth,
color: Colors.grey,
size: 100,
),
),
Text(
'Scan For Devices',
style: TextStyle(color: Colors.grey, fontSize: 22),
)
],
),
)
: _buildListView(),
],
),
);
}
Widget _buildButtons() {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: LayoutBuilder(builder: (context, constraints) {
const tileWidth = 150;
const tileHeight = 500;
final count = constraints.maxWidth ~/ tileWidth;
return GridView.count(
crossAxisCount: count,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: tileHeight / tileWidth,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
ElevatedButton(
child: const Text('Enable Bluetooth'),
onPressed: () async {
bool isEnabled = await UniversalBle.enableBluetooth();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("BluetoothEnabled: $isEnabled"),
),
);
},
),
ElevatedButton(
child: const Text('Permissions'),
onPressed: () async {
bool hasPermissions =
await Validator.arePermissionsGranted();
if (hasPermissions) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Permissions already granted"),
),
);
}
},
),
ElevatedButton(
child: const Text('Start Scan'),
onPressed: () {
setState(() {
_scanResults.clear();
});
UniversalBle.startScan(webRequestOptions: requestOptions);
},
),
ElevatedButton(
child: const Text('Stop Scan'),
onPressed: () {
UniversalBle.stopScan();
},
),
ElevatedButton(
child: const Text('Connected Devices'),
onPressed: () async {
var devices = await UniversalBle.getConnectedDevices(
withServices: services,
);
if (devices.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("No Connected Devices Found"),
),
);
}
// print(devices.map((e) => e.toJson()));
setState(() {
_scanResults.clear();
_scanResults.addAll(devices);
});
},
),
],
);
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Ble Availability : ${bleAvailabilityState?.name}',
),
),
IconButton(
onPressed: () {
setState(() {
_scanResults.clear();
});
},
icon: const Icon(Icons.close_rounded)),
],
),
],
);
}
Widget _buildListView() {
return Expanded(
child: ListView.separated(
itemBuilder: (context, index) {
var scanResult = _scanResults[index];
String? name = scanResult.name;
if (name == null || name.isEmpty) {
name = 'NA';
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Card(
child: ListTile(
title: Text(
'$name (${scanResult.rssi})',
),
subtitle: scanResult.isPaired != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${scanResult.deviceId} "),
scanResult.isPaired == true
? const Text(
"Paired",
style: TextStyle(color: Colors.green),
)
: const Text(
"Not Paired",
style: TextStyle(color: Colors.red),
),
],
)
: Text(scanResult.deviceId),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
print("$name: ${scanResult.deviceId}");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
PeripheralDetailPage(scanResult.deviceId, false),
));
},
),
),
);
},
separatorBuilder: (context, index) => const Divider(),
itemCount: _scanResults.length,
),
);
}
}
+662
View File
@@ -0,0 +1,662 @@
// ignore_for_file: avoid_print, depend_on_referenced_packages
import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/main.dart';
String gssUuid(String code) => '0000$code-0000-1000-8000-00805f9b34fb';
class PeripheralDetailPage extends StatefulWidget {
final String deviceId;
final bool autoConnect;
const PeripheralDetailPage(this.deviceId, this.autoConnect, {Key? key})
: super(key: key);
@override
State<StatefulWidget> createState() {
return _PeripheralDetailPageState();
}
}
class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
bool isConnected = false;
bool parseValue = true;
GlobalKey<FormState> uuidFormKey = GlobalKey<FormState>();
GlobalKey<FormState> valueFormKey = GlobalKey<FormState>();
List<BleService> discoveredServices = [];
ExpandableController expandableController =
ExpandableController(initialExpanded: autoConnect);
List<String> results = [];
@override
void initState() {
super.initState();
UniversalBle.onConnectionChanged = _handleConnectionChange;
UniversalBle.onValueChanged = _handleValueChange;
UniversalBle.onPairStateChange =
(String deviceId, bool isPaired, String? error) {
print('OnPairStateChange $deviceId, $isPaired');
setState(() {
if (error != null) {
results.add("PairStateChangeError (Paired: $isPaired): $error ");
} else {
results.add('PairStateChange: $isPaired');
}
});
};
if (autoConnect) {
UniversalBle.connect(widget.deviceId);
}
}
@override
void dispose() {
super.dispose();
UniversalBle.onConnectionChanged = null;
UniversalBle.onValueChanged = null;
// Disconnect when leaving the page
if (isConnected) UniversalBle.disconnect(widget.deviceId);
}
void _handleConnectionChange(String deviceId, BleConnectionState state) {
print('_handleConnectionChange $deviceId, ${state.name}');
setState(() {
if (deviceId == widget.deviceId) {
isConnected = (state == BleConnectionState.connected);
}
results.add('Connection: ${state.name.toUpperCase()}');
});
// Auto Discover Services
if (isConnected) {
discoverServices();
} else if (autoConnect && !isConnected) {
print("Trying to reconnect");
UniversalBle.connect(widget.deviceId);
}
}
void discoverServices() async {
var services = await UniversalBle.discoverServices(widget.deviceId);
print('${services.length} services discovered');
discoveredServices.clear();
setState(() {
discoveredServices = services;
});
}
void _handleValueChange(
String deviceId, String characteristicId, Uint8List value) {
String s = String.fromCharCodes(value);
String data =
parseValue ? '$s\nraw : ${value.toString()}' : value.toString();
print('_handleValueChange $deviceId, $characteristicId, $s');
setState(() {
results.add("Value: $data");
});
}
Uint8List? _getWriteValue() {
if (!uuidFormKey.currentState!.validate() ||
!valueFormKey.currentState!.validate()) {
return null;
}
if (binaryCode.text.isEmpty) {
print("Error: No value to write");
return null;
}
List<int>? value;
String text = binaryCode.text;
if (text.contains(",") || text.contains("[") || text.contains("]")) {
text = text.replaceAll("[", "").replaceAll("]", "").trim();
value = text.split(',').map(int.parse).toList();
} else {
try {
value = hex.decode(text);
} catch (e) {
print("Error parsing hex $e");
print("Trying utf8 encoding");
value = utf8.encode(text);
}
}
return Uint8List.fromList(value);
}
final serviceUUID = TextEditingController();
final characteristicUUID = TextEditingController();
final binaryCode = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Peripheral Details'),
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.circle,
color: isConnected ? Colors.greenAccent : Colors.red,
size: 20,
),
)
],
),
body: LayoutBuilder(builder: (context, constraints) {
bool isDesktop = constraints.maxWidth > 1000;
return Row(
children: [
if (isDesktop)
Expanded(
flex: 1,
child: Container(
color: Colors.grey[300],
child: discoveredServices.isEmpty
? const Center(
child: Text('No Services Discovered'),
)
: ServicesView(
discoveredServices: discoveredServices,
scrollable: true,
onTap: (BleService service,
BleCharacteristic characteristic) {
serviceUUID.text = service.uuid;
characteristicUUID.text = characteristic.uuid;
print(
characteristic.properties.map((e) => e.name),
);
},
),
),
),
Expanded(
flex: 3,
child: Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Form(
key: uuidFormKey,
child: Column(
children: [
// Top buttons
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
PlatformButton(
text: 'Connect',
enabled: !isConnected,
onPressed: () async {
try {
await UniversalBle.connect(widget.deviceId);
} catch (e) {
print(e);
setState(() {
results.add('ConnectError : $e');
});
}
},
),
PlatformButton(
text: 'Disconnect',
enabled: isConnected,
onPressed: () {
UniversalBle.disconnect(widget.deviceId);
},
),
],
),
),
const Divider(),
// Text Fields
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: serviceUUID,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a service uuid';
}
return null;
},
decoration: const InputDecoration(
labelText: 'ServiceUUID',
border: OutlineInputBorder(),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: characteristicUUID,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a Characteristic uuid';
}
return null;
},
decoration: const InputDecoration(
labelText: 'CharacteristicUUID',
border: OutlineInputBorder(),
),
),
),
Form(
key: valueFormKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
flex: 5,
child: TextFormField(
controller: binaryCode,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a value';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Enter Value ',
hintText:
"Enter Value (String, Hex or List<int>)",
border: OutlineInputBorder(),
),
),
),
Switch(
value: parseValue,
onChanged: (value) {
setState(() {
parseValue = value;
});
}),
],
),
),
),
const Divider(),
// Buttons
Padding(
padding: const EdgeInsets.all(8.0),
child: LayoutBuilder(builder: (context, constraints) {
const tileWidth = 150;
const tileHeight = 500;
final count = constraints.maxWidth ~/ tileWidth;
return GridView.count(
crossAxisCount: count,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: tileHeight / tileWidth,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
PlatformButton(
enabled: isConnected,
onPressed: () {
Uint8List? value = _getWriteValue();
if (value == null) return;
print("Writing $value");
UniversalBle.writeValue(
widget.deviceId,
serviceUUID.text,
characteristicUUID.text,
value,
BleOutputProperty.withResponse);
},
text: 'Write',
),
PlatformButton(
enabled: isConnected,
onPressed: () async {
if (!uuidFormKey.currentState!.validate()) {
return;
}
try {
Uint8List value =
await UniversalBle.readValue(
widget.deviceId,
serviceUUID.text,
characteristicUUID.text);
String s = String.fromCharCodes(value);
String data = parseValue
? '$s\nraw : ${value.toString()}'
: value.toString();
setState(() {
results.add('Read : $data');
});
} catch (e) {
print(e);
setState(() {
results.add('ReadError : $e');
});
}
},
text: 'Read',
),
PlatformButton(
enabled: isConnected,
onPressed: () async {
int mtu = await UniversalBle.requestMtu(
widget.deviceId, 247);
setState(() {
results.add('MTU : $mtu');
});
},
text: 'Request Mtu',
),
PlatformButton(
enabled: isConnected,
onPressed: () {
if (!uuidFormKey.currentState!.validate()) {
return;
}
BleCharacteristic? characteristic;
for (BleService service
in discoveredServices) {
for (BleCharacteristic c
in service.characteristics) {
if (c.uuid == characteristicUUID.text) {
characteristic = c;
break;
}
}
}
BleInputProperty inputProperty =
BleInputProperty.notification;
if (characteristic != null) {
if (characteristic.properties.contains(
CharacteristicProperty.indicate)) {
inputProperty =
BleInputProperty.indication;
} else if (characteristic.properties
.contains(
CharacteristicProperty.notify)) {
inputProperty =
BleInputProperty.notification;
} else {
print('No notify or indicate property');
return;
}
}
print(inputProperty);
UniversalBle.setNotifiable(
widget.deviceId,
serviceUUID.text,
characteristicUUID.text,
inputProperty,
);
},
text: 'Notify/Indicate',
),
PlatformButton(
enabled: isConnected,
onPressed: () {
if (!uuidFormKey.currentState!.validate()) {
return;
}
UniversalBle.setNotifiable(
widget.deviceId,
serviceUUID.text,
characteristicUUID.text,
BleInputProperty.disabled,
);
},
text: 'Cancel Notify',
),
PlatformButton(
onPressed: () async {
discoverServices();
},
enabled: isConnected,
text: 'Discover Services',
),
PlatformButton(
onPressed: () async {
await UniversalBle.pair(widget.deviceId);
},
text: 'Pair',
),
PlatformButton(
onPressed: () async {
bool isPaired = await UniversalBle.isPaired(
widget.deviceId);
setState(() {
results.add('IsPaired : $isPaired');
});
},
text: 'IsPaired',
),
PlatformButton(
onPressed: () async {
await UniversalBle.unPair(widget.deviceId);
},
text: 'UnPair',
),
PlatformButton(
onPressed: () async {
setState(() {
results.clear();
discoveredServices.clear();
});
},
text: 'Clear',
),
],
);
}),
),
// Results , Services
if (!isDesktop)
Column(
children: [
ResultView(
results: results,
onClearTap: (int? index) {
setState(() {
if (index != null) {
results.removeAt(index);
} else {
results.clear();
}
});
}),
const Divider(),
ServicesView(
discoveredServices: discoveredServices,
onTap: (BleService service,
BleCharacteristic characteristic) {
serviceUUID.text = service.uuid;
characteristicUUID.text = characteristic.uuid;
print(
characteristic.properties
.map((e) => e.name),
);
},
),
],
)
],
),
),
),
),
),
// For Desktop
if (isDesktop)
Expanded(
flex: 1,
child: Container(
color: Colors.grey[300],
child: results.isEmpty
? const Center(
child: Text('No results'),
)
: ResultView(
results: results,
scrollable: true,
onClearTap: (int? index) {
setState(() {
if (index != null) {
results.removeAt(index);
} else {
results.clear();
}
});
}),
)),
],
);
}),
);
}
}
class ResultView extends StatelessWidget {
final List<String> results;
final bool scrollable;
final Function(int? index) onClearTap;
const ResultView({
required this.results,
required this.onClearTap,
this.scrollable = false,
super.key,
});
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: !scrollable,
physics: scrollable ? null : const NeverScrollableScrollPhysics(),
itemCount: results.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: ListTile(
onTap: () {
onClearTap(index);
},
title: Text(results[index]),
trailing: const Icon(Icons.clear),
),
),
);
},
);
}
}
class ServicesView extends StatelessWidget {
final List<BleService> discoveredServices;
final bool scrollable;
final Function(BleService service, BleCharacteristic characteristic)? onTap;
const ServicesView({
super.key,
required this.discoveredServices,
this.onTap,
this.scrollable = false,
});
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: !scrollable,
physics: scrollable ? null : const NeverScrollableScrollPhysics(),
itemCount: discoveredServices.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: ExpandablePanel(
header: Container(
color: scrollable ? Colors.lime : Colors.grey[300],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Icon(Icons.arrow_forward_ios),
Expanded(child: Text(discoveredServices[index].uuid)),
],
),
),
),
collapsed: const SizedBox(),
expanded: Column(
children: discoveredServices[index]
.characteristics
.map((e) => Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () {
onTap?.call(discoveredServices[index], e);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.arrow_right_outlined),
Expanded(child: Text(e.uuid)),
],
),
Text(
"Properties: ${e.properties.map((e) => e.name)}",
)
],
),
),
],
),
))
.toList(),
),
),
),
);
},
);
}
}
class PlatformButton extends StatelessWidget {
final String text;
final Function()? onPressed;
final bool enabled;
const PlatformButton({
required this.text,
required this.onPressed,
this.enabled = true,
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: enabled ? onPressed : null,
child: Text(text),
);
}
}
+102
View File
@@ -0,0 +1,102 @@
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:permission_handler/permission_handler.dart';
class Validator {
/*
Required Permissions :
<----------->
IOS :
- Bluetooth
<----------->
Android :
if AndroidVersions < 12
- Location
- Bluetooth
else
- Bluetooth Scan
- Bluetooth Connect
<----------->
Macos :
<----------->
Windows : None
<----------->
Linux : None
<----------->
Web :
Check if Browser Supports Bluetooth
*/
static Future<bool> arePermissionsGranted() async {
if (!isMobilePlatform) return true;
var status = await _permissionStatus;
bool blePermissionGranted = status[0];
bool locationPermissionGranted = status[1];
if (locationPermissionGranted && blePermissionGranted) return true;
if (!blePermissionGranted) {
PermissionStatus blePermissionCheck =
await Permission.bluetooth.request();
if (blePermissionCheck.isPermanentlyDenied) {
print("Bluetooth Permission Permanently Denied");
openAppSettings();
}
return false;
}
if (!locationPermissionGranted) {
PermissionStatus locationPermissionCheck =
await Permission.location.request();
if (locationPermissionCheck.isPermanentlyDenied) {
print("Location Permission Permanently Denied");
openAppSettings();
}
return false;
}
return false;
}
static Future<List<bool>> get _permissionStatus async {
bool blePermissionGranted = false;
bool locationPermissionGranted = false;
if (await requiresExplicitAndroidBluetoothPermissions) {
bool bleConnectPermission =
(await Permission.bluetoothConnect.request()).isGranted;
bool bleScanPermission =
(await Permission.bluetoothScan.request()).isGranted;
blePermissionGranted = bleConnectPermission && bleScanPermission;
locationPermissionGranted = true;
} else {
PermissionStatus permissionStatus = await Permission.bluetooth.request();
blePermissionGranted = permissionStatus.isGranted;
locationPermissionGranted = await requiresLocationPermission
? (await Permission.locationWhenInUse.request()).isGranted
: true;
}
return [locationPermissionGranted, blePermissionGranted];
}
static bool get isMobilePlatform =>
!kIsWeb && (Platform.isAndroid || Platform.isIOS);
static Future<bool> get requiresLocationPermission async =>
!kIsWeb &&
Platform.isAndroid &&
(!await requiresExplicitAndroidBluetoothPermissions);
static Future<bool> get requiresExplicitAndroidBluetoothPermissions async {
if (kIsWeb || !Platform.isAndroid) return false;
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
return androidInfo.version.sdkInt >= 31;
}
}
+1
View File
@@ -0,0 +1 @@
flutter/ephemeral
+141
View File
@@ -0,0 +1,141 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "universal_ble_example")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.navideck.universal_ble")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Enable the test target.
set(include_universal_ble_tests TRUE)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
+88
View File
@@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)
@@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}
@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
@@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
+6
View File
@@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
+104
View File
@@ -0,0 +1,104 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "universal_ble_example");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "universal_ble_example");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

Some files were not shown because too many files have changed in this diff Show More