Initial commit
@@ -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.
|
||||
@@ -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/
|
||||
@@ -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'
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
## 0.8.0
|
||||
|
||||
* Initial release.
|
||||
@@ -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.
|
||||
@@ -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());
|
||||
```
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'universal_ble'
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
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>
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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_
|
||||