refactor: 重构项目 (#3)
This commit is contained in:
1
example/.gitignore
vendored
1
example/.gitignore
vendored
@ -8,6 +8,7 @@
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
|
@ -1,10 +0,0 @@
|
||||
# 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: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
@ -8,9 +8,9 @@ This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||
- [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, view our
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
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.
|
||||
|
29
example/analysis_options.yaml
Normal file
29
example/analysis_options.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
# 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-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# 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
|
2
example/android/.gitignore
vendored
2
example/android/.gitignore
vendored
@ -9,3 +9,5 @@ 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
|
||||
|
@ -1,8 +1,7 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,16 +25,29 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
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 "dev.yanshouwang.bluetooth_low_energy_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="dev.yanshouwang.bluetooth_low_energy_example">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
<!-- 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"/>
|
||||
|
@ -2,9 +2,11 @@
|
||||
package="dev.yanshouwang.bluetooth_low_energy_example">
|
||||
<application
|
||||
android:label="bluetooth_low_energy_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"
|
||||
@ -18,15 +20,6 @@
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -3,14 +3,14 @@
|
||||
<!-- 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
|
||||
Flutter draws its first frame -->
|
||||
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>
|
||||
|
@ -3,14 +3,14 @@
|
||||
<!-- 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
|
||||
Flutter draws its first frame -->
|
||||
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>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="dev.yanshouwang.bluetooth_low_energy_example">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
<!-- 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"/>
|
||||
|
@ -1,13 +1,12 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.21'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
@ -16,13 +15,14 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
|
@ -3,5 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-6.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
example/ios/.gitignore
vendored
1
example/ios/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
|
@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
<string>11.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '9.0'
|
||||
# platform :ios, '11.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
@ -1,9 +1,9 @@
|
||||
PODS:
|
||||
- bluetooth_low_energy (0.0.1):
|
||||
- Flutter
|
||||
- SwiftProtobuf (~> 1.0)
|
||||
- SwiftProtobuf (~> 1.20)
|
||||
- Flutter (1.0.0)
|
||||
- SwiftProtobuf (1.17.0)
|
||||
- SwiftProtobuf (1.20.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- bluetooth_low_energy (from `.symlinks/plugins/bluetooth_low_energy/ios`)
|
||||
@ -20,10 +20,10 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
bluetooth_low_energy: 9222b99f977897165e6ffaece386bd289ac8c1fb
|
||||
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
||||
SwiftProtobuf: 9c85136c6ba74b0a1b84279dbf0f6db8efb714e0
|
||||
bluetooth_low_energy: af34d921ca3a9e085cf6364c500050965acf25f9
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
SwiftProtobuf: e40a7684079620e84ba522dbaeab0cddb0ec7ffd
|
||||
|
||||
PODFILE CHECKSUM: a75497545d4391e2d394c3668e20cfb1c2bbd4aa
|
||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||
|
||||
COCOAPODS: 1.10.1
|
||||
COCOAPODS: 1.11.3
|
||||
|
@ -13,7 +13,7 @@
|
||||
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 */; };
|
||||
EA280E36114EEC52A66510AF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F6363DB0F6B8F760A0B70E6 /* Pods_Runner.framework */; };
|
||||
BC25F992B6ECFCC18D953308 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3248507C6DD52333A770C1CB /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@ -30,11 +30,11 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1173070BE6CFE927D74C0736 /* 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>"; };
|
||||
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>"; };
|
||||
2F6363DB0F6B8F760A0B70E6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3248507C6DD52333A770C1CB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
5CB2CF9D872B54AC77EFD5AD /* 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>"; };
|
||||
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>"; };
|
||||
@ -45,8 +45,8 @@
|
||||
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>"; };
|
||||
9E8C4C982EC9200D28D9557C /* 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>"; };
|
||||
FA6DAFE6FDB9F8D1C4A8AEF3 /* 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>"; };
|
||||
AEA309A3A59074B58910681F /* 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>"; };
|
||||
C3AC24B274EC26E645389946 /* 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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -54,13 +54,21 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EA280E36114EEC52A66510AF /* Pods_Runner.framework in Frameworks */,
|
||||
BC25F992B6ECFCC18D953308 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
2B77126ACFA0B323BF67EC4C /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3248507C6DD52333A770C1CB /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -78,8 +86,8 @@
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
A19CF493038BEE74587FB0E8 /* Pods */,
|
||||
990CDB7BA621E0B7AFA0C6BE /* Frameworks */,
|
||||
F7511A57B6D81C59C9E0353E /* Pods */,
|
||||
2B77126ACFA0B323BF67EC4C /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -106,20 +114,12 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
990CDB7BA621E0B7AFA0C6BE /* Frameworks */ = {
|
||||
F7511A57B6D81C59C9E0353E /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2F6363DB0F6B8F760A0B70E6 /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A19CF493038BEE74587FB0E8 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FA6DAFE6FDB9F8D1C4A8AEF3 /* Pods-Runner.debug.xcconfig */,
|
||||
1173070BE6CFE927D74C0736 /* Pods-Runner.release.xcconfig */,
|
||||
9E8C4C982EC9200D28D9557C /* Pods-Runner.profile.xcconfig */,
|
||||
AEA309A3A59074B58910681F /* Pods-Runner.debug.xcconfig */,
|
||||
C3AC24B274EC26E645389946 /* Pods-Runner.release.xcconfig */,
|
||||
5CB2CF9D872B54AC77EFD5AD /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
@ -131,14 +131,14 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
4DEF012D22BCBC3AF731DE9C /* [CP] Check Pods Manifest.lock */,
|
||||
224727E4C00C24BC3CD4292A /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
EDB3731BEABF1E16CE6A52EB /* [CP] Embed Pods Frameworks */,
|
||||
DCBB4C37F33FFC666159F299 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -155,7 +155,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1020;
|
||||
LastUpgradeCheck = 1300;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@ -197,21 +197,7 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
4DEF012D22BCBC3AF731DE9C /* [CP] Check Pods Manifest.lock */ = {
|
||||
224727E4C00C24BC3CD4292A /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@ -233,6 +219,20 @@
|
||||
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;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -247,7 +247,7 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
EDB3731BEABF1E16CE6A52EB /* [CP] Embed Pods Frameworks */ = {
|
||||
DCBB4C37F33FFC666159F299 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@ -339,7 +339,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@ -417,7 +417,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@ -466,7 +466,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@ -27,8 +27,6 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@ -38,8 +36,8 @@
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -61,8 +59,6 @@
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
|
@ -2,8 +2,12 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Bluetooth Low Energy</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@ -23,7 +27,7 @@
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>We need to use your bluetooth to communicate with other BLE devices.</string>
|
||||
<string>Let's try to communicate use the bluetooth low energy!</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
@ -7,84 +7,91 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() {
|
||||
final app = MaterialApp(
|
||||
theme: ThemeData(
|
||||
fontFamily: 'IBM Plex Mono',
|
||||
),
|
||||
home: HomeView(),
|
||||
routes: {
|
||||
'gatt': (context) => GattView(),
|
||||
},
|
||||
);
|
||||
const app = MyApp();
|
||||
runApp(app);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: const HomeView(),
|
||||
routes: {
|
||||
'device': (context) {
|
||||
final uuid = ModalRoute.of(context)?.settings.arguments as UUID;
|
||||
return DeviceView(
|
||||
uuid: uuid,
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
const HomeView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HomeViewState createState() => _HomeViewState();
|
||||
State<HomeView> createState() => _HomeViewState();
|
||||
}
|
||||
|
||||
class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
||||
final ValueNotifier<bool> state;
|
||||
final ValueNotifier<bool> discovering;
|
||||
final ValueNotifier<Map<UUID, Discovery>> discoveries;
|
||||
late StreamSubscription<BluetoothState> stateSubscription;
|
||||
late StreamSubscription<Discovery> discoverySubscription;
|
||||
|
||||
_HomeViewState()
|
||||
: state = ValueNotifier(false),
|
||||
discovering = ValueNotifier(false),
|
||||
discoveries = ValueNotifier({});
|
||||
class _HomeViewState extends State<HomeView> {
|
||||
late ValueNotifier<bool> state;
|
||||
late ValueNotifier<bool> discovering;
|
||||
late ValueNotifier<List<Advertisement>> advertisements;
|
||||
late Stream<Advertisement> advertisementStream;
|
||||
late StreamSubscription<BluetoothState> stateStreamSubscription;
|
||||
late StreamSubscription<Advertisement> advertisementStreamSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance!.addObserver(this);
|
||||
|
||||
state = ValueNotifier(false);
|
||||
discovering = ValueNotifier(false);
|
||||
advertisements = ValueNotifier([]);
|
||||
advertisementStream = CentralManager.instance.getAdvertisementStream();
|
||||
|
||||
state.addListener(onStateChanged);
|
||||
stateStreamSubscription = CentralManager.instance.stateStream.listen(
|
||||
(state) => this.state.value = state == BluetoothState.poweredOn,
|
||||
);
|
||||
setup();
|
||||
}
|
||||
|
||||
void setup() async {
|
||||
final state = await central.state;
|
||||
final authorized = await CentralManager.instance.authorize();
|
||||
if (!authorized) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
final state = await CentralManager.instance.getState();
|
||||
this.state.value = state == BluetoothState.poweredOn;
|
||||
stateSubscription = central.stateChanged.listen(
|
||||
(state) {
|
||||
this.state.value = state == BluetoothState.poweredOn;
|
||||
final invisible = !ModalRoute.of(context)!.isCurrent;
|
||||
if (invisible) return;
|
||||
if (this.state.value) {
|
||||
startDiscovery();
|
||||
} else {
|
||||
discovering.value = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
discoverySubscription = central.discovered.listen(
|
||||
(discovery) {
|
||||
discoveries.value[discovery.uuid] = discovery;
|
||||
discoveries.value = {...discoveries.value};
|
||||
},
|
||||
);
|
||||
if (this.state.value) {
|
||||
startDiscovery();
|
||||
}
|
||||
|
||||
void onStateChanged() {
|
||||
final route = ModalRoute.of(context);
|
||||
if (route == null || !route.isCurrent) {
|
||||
return;
|
||||
}
|
||||
if (state.value) {
|
||||
startScan();
|
||||
} else {
|
||||
discovering.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
stopDiscovery();
|
||||
stateSubscription.cancel();
|
||||
discoverySubscription.cancel();
|
||||
discoveries.dispose();
|
||||
stopScan();
|
||||
state.removeListener(onStateChanged);
|
||||
stateStreamSubscription.cancel();
|
||||
|
||||
state.dispose();
|
||||
discovering.dispose();
|
||||
WidgetsBinding.instance!.removeObserver(this);
|
||||
print('dispose');
|
||||
advertisements.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -92,89 +99,108 @@ class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Home'),
|
||||
title: const Text('Home'),
|
||||
),
|
||||
body: bodyView,
|
||||
body: buildBody(context),
|
||||
);
|
||||
}
|
||||
|
||||
void startDiscovery() async {
|
||||
if (discovering.value || !state.value) return;
|
||||
await central.startDiscovery();
|
||||
void startScan() async {
|
||||
if (discovering.value || !state.value) {
|
||||
return;
|
||||
}
|
||||
advertisementStreamSubscription = advertisementStream.listen(
|
||||
(advertisement) {
|
||||
final index = advertisements.value.indexWhere(
|
||||
(element) => element.uuid == advertisement.uuid,
|
||||
);
|
||||
if (index >= 0) {
|
||||
return;
|
||||
}
|
||||
advertisements.value = [...advertisements.value, advertisement];
|
||||
},
|
||||
);
|
||||
discovering.value = true;
|
||||
}
|
||||
|
||||
void stopDiscovery() async {
|
||||
if (!discovering.value || !state.value) return;
|
||||
await central.stopDiscovery();
|
||||
discoveries.value = {};
|
||||
void stopScan() async {
|
||||
if (!discovering.value || !state.value) {
|
||||
return;
|
||||
}
|
||||
await advertisementStreamSubscription.cancel();
|
||||
advertisements.value = [];
|
||||
discovering.value = false;
|
||||
}
|
||||
|
||||
void showAdvertisements(Discovery discovery) {
|
||||
void showAdvertisement(Advertisement advertisement) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0.0,
|
||||
builder: (context) => buildAdvertisementsView(discovery),
|
||||
builder: (context) => buildAdvertisementView(advertisement),
|
||||
);
|
||||
}
|
||||
|
||||
void showGattView(Discovery discovery) async {
|
||||
stopDiscovery();
|
||||
void showDeviceView(UUID uuid) async {
|
||||
stopScan();
|
||||
await Navigator.of(context).pushNamed(
|
||||
'gatt',
|
||||
arguments: discovery.uuid,
|
||||
'device',
|
||||
arguments: uuid,
|
||||
);
|
||||
startDiscovery();
|
||||
startScan();
|
||||
}
|
||||
}
|
||||
|
||||
extension on _HomeViewState {
|
||||
Widget get bodyView {
|
||||
Widget buildBody(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: state,
|
||||
builder: (context, bool state, child) =>
|
||||
state ? discoveriesView : closedView,
|
||||
builder: (context, bool state, child) {
|
||||
return state
|
||||
? buildAdvertisementsView(context)
|
||||
: buildClosedView(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget get closedView {
|
||||
return Center(
|
||||
Widget buildClosedView(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('蓝牙未开启'),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get discoveriesView {
|
||||
Widget buildAdvertisementsView(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async => discoveries.value = {},
|
||||
onRefresh: () async => advertisements.value = [],
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: discoveries,
|
||||
builder: (context, Map<UUID, Discovery> discoveries, child) {
|
||||
valueListenable: advertisements,
|
||||
builder: (context, List<Advertisement> advertisements, child) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.all(6.0),
|
||||
itemCount: discoveries.length,
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
itemCount: advertisements.length,
|
||||
itemBuilder: (context, i) {
|
||||
final discovery = discoveries.values.elementAt(i);
|
||||
final advertisement = advertisements.elementAt(i);
|
||||
final connectable = advertisement.connectable ?? true;
|
||||
return Card(
|
||||
color: discovery.connectable ? Colors.amber : Colors.grey,
|
||||
color: connectable ? Colors.amber : Colors.grey,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: BeveledRectangleBorder(
|
||||
shape: const BeveledRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(12.0),
|
||||
bottomLeft: Radius.circular(12.0)),
|
||||
topRight: Radius.circular(12.0),
|
||||
bottomLeft: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
margin: EdgeInsets.all(6.0),
|
||||
key: Key(discovery.uuid.name),
|
||||
margin: const EdgeInsets.all(6.0),
|
||||
key: Key('${advertisement.uuid}'),
|
||||
child: InkWell(
|
||||
splashColor: Colors.purple,
|
||||
onTap: discovery.connectable
|
||||
? () => showGattView(discovery)
|
||||
onTap: connectable
|
||||
? () => showDeviceView(advertisement.uuid)
|
||||
: null,
|
||||
onLongPress: () => showAdvertisements(discovery),
|
||||
onLongPress: () => showAdvertisement(advertisement),
|
||||
child: Container(
|
||||
height: 100.0,
|
||||
padding: EdgeInsets.all(12.0),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
@ -183,9 +209,9 @@ extension on _HomeViewState {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(discovery.name ?? 'NaN'),
|
||||
Text(advertisement.localName ?? 'UNKNOWN'),
|
||||
Text(
|
||||
discovery.uuid.name,
|
||||
advertisement.uuid.toString(),
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
@ -194,7 +220,7 @@ extension on _HomeViewState {
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
discovery.rssi.toString(),
|
||||
advertisement.rssi.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
@ -210,10 +236,10 @@ extension on _HomeViewState {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildAdvertisementsView(Discovery discovery) {
|
||||
Widget buildAdvertisementView(Advertisement advertisement) {
|
||||
final widgets = <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: const [
|
||||
Text('Type'),
|
||||
Expanded(
|
||||
child: Center(
|
||||
@ -222,34 +248,34 @@ extension on _HomeViewState {
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
const Divider(),
|
||||
];
|
||||
for (final entry in discovery.advertisements.entries) {
|
||||
final key = entry.key.toRadixString(16).padLeft(2, '0');
|
||||
final value = hex.encode(entry.value);
|
||||
final widget = Row(
|
||||
children: [
|
||||
Text('0x$key'),
|
||||
Container(width: 12.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'$value',
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
widgets.add(widget);
|
||||
if (entry.key != discovery.advertisements.entries.last.key) {
|
||||
final divider = Divider();
|
||||
widgets.add(divider);
|
||||
}
|
||||
}
|
||||
// for (final entry in advertisement.data.entries) {
|
||||
// final type = '0x${entry.key.toRadixString(16).padLeft(2, '0')}';
|
||||
// final value = hex.encode(entry.value);
|
||||
// final widget = Row(
|
||||
// children: [
|
||||
// Text(type),
|
||||
// Container(width: 12.0),
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// value,
|
||||
// softWrap: true,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// widgets.add(widget);
|
||||
// if (entry.key != advertisement.data.entries.last.key) {
|
||||
// const divider = Divider();
|
||||
// widgets.add(divider);
|
||||
// }
|
||||
// }
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(12.0),
|
||||
child: Material(
|
||||
elevation: 1.0,
|
||||
shape: BeveledRectangleBorder(
|
||||
shape: const BeveledRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12.0),
|
||||
bottomRight: Radius.circular(12.0),
|
||||
@ -268,49 +294,51 @@ extension on _HomeViewState {
|
||||
}
|
||||
}
|
||||
|
||||
class GattView extends StatefulWidget {
|
||||
const GattView({Key? key}) : super(key: key);
|
||||
class DeviceView extends StatefulWidget {
|
||||
final UUID uuid;
|
||||
|
||||
const DeviceView({
|
||||
Key? key,
|
||||
required this.uuid,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_GattViewState createState() => _GattViewState();
|
||||
State<DeviceView> createState() => _DeviceViewState();
|
||||
}
|
||||
|
||||
class _GattViewState extends State<GattView> {
|
||||
final ValueNotifier<ConnectionState> state;
|
||||
GATT? gatt;
|
||||
StreamSubscription? connectionLostSubscription;
|
||||
final ValueNotifier<GattService?> service;
|
||||
final ValueNotifier<GattCharacteristic?> characteristic;
|
||||
final TextEditingController writeController;
|
||||
final ValueNotifier<Map<GattCharacteristic, StreamSubscription>> notifies;
|
||||
final ValueNotifier<List<String>> logs;
|
||||
|
||||
late UUID uuid;
|
||||
|
||||
_GattViewState()
|
||||
: state = ValueNotifier(ConnectionState.disconnected),
|
||||
service = ValueNotifier(null),
|
||||
characteristic = ValueNotifier(null),
|
||||
writeController = TextEditingController(),
|
||||
notifies = ValueNotifier({}),
|
||||
logs = ValueNotifier([]);
|
||||
class _DeviceViewState extends State<DeviceView> {
|
||||
late Peripheral peripheral;
|
||||
late Map<GattService, List<GattCharacteristic>> services;
|
||||
late ValueNotifier<ConnectionState> state;
|
||||
late ValueNotifier<GattService?> service;
|
||||
late ValueNotifier<GattCharacteristic?> characteristic;
|
||||
late TextEditingController writeController;
|
||||
late ValueNotifier<Map<GattCharacteristic, StreamSubscription>> notifies;
|
||||
late ValueNotifier<List<String>> logs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
services = {};
|
||||
state = ValueNotifier(ConnectionState.disconnected);
|
||||
service = ValueNotifier(null);
|
||||
characteristic = ValueNotifier(null);
|
||||
writeController = TextEditingController();
|
||||
notifies = ValueNotifier({});
|
||||
logs = ValueNotifier([]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
uuid = ModalRoute.of(context)!.settings.arguments as UUID;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(uuid.name),
|
||||
title: Text('${widget.uuid}'),
|
||||
actions: [
|
||||
changeStateView,
|
||||
buildConnectionState(context),
|
||||
],
|
||||
),
|
||||
body: bodyView,
|
||||
body: buildBody(context),
|
||||
);
|
||||
}
|
||||
|
||||
@ -356,16 +384,12 @@ class _GattViewState extends State<GattView> {
|
||||
void connect() async {
|
||||
try {
|
||||
state.value = ConnectionState.connecting;
|
||||
gatt = await central.connect(uuid);
|
||||
state.value = ConnectionState.connected;
|
||||
connectionLostSubscription = gatt!.connectionLost.listen(
|
||||
(errorCode) async {
|
||||
peripheral = await CentralManager.instance.connect(
|
||||
widget.uuid,
|
||||
onConnectionLost: (error) {
|
||||
for (var subscription in notifies.value.values) {
|
||||
await subscription.cancel();
|
||||
subscription.cancel();
|
||||
}
|
||||
await connectionLostSubscription!.cancel();
|
||||
gatt = null;
|
||||
connectionLostSubscription = null;
|
||||
service.value = null;
|
||||
characteristic.value = null;
|
||||
notifies.value.clear();
|
||||
@ -373,7 +397,24 @@ class _GattViewState extends State<GattView> {
|
||||
state.value = ConnectionState.disconnected;
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
try {
|
||||
final items0 = <GattService, List<GattCharacteristic>>{};
|
||||
final services = await peripheral.discoverServices();
|
||||
for (var service in services) {
|
||||
final items1 = <GattCharacteristic>[];
|
||||
final characteristics = await service.discoverCharacteristics();
|
||||
for (var characteristic in characteristics) {
|
||||
items1.add(characteristic);
|
||||
}
|
||||
items0[service] = items1;
|
||||
}
|
||||
this.services = items0;
|
||||
} catch (e) {
|
||||
peripheral.disconnect();
|
||||
rethrow;
|
||||
}
|
||||
state.value = ConnectionState.connected;
|
||||
} catch (error) {
|
||||
state.value = ConnectionState.disconnected;
|
||||
}
|
||||
}
|
||||
@ -381,32 +422,30 @@ class _GattViewState extends State<GattView> {
|
||||
void disconnect() async {
|
||||
try {
|
||||
state.value = ConnectionState.disconnecting;
|
||||
await gatt!.disconnect();
|
||||
for (var subscription in notifies.value.values) {
|
||||
await subscription.cancel();
|
||||
subscription.cancel();
|
||||
}
|
||||
await connectionLostSubscription!.cancel();
|
||||
gatt = null;
|
||||
connectionLostSubscription = null;
|
||||
await peripheral.disconnect();
|
||||
services = {};
|
||||
service.value = null;
|
||||
characteristic.value = null;
|
||||
notifies.value.clear();
|
||||
logs.value.clear();
|
||||
state.value = ConnectionState.disconnected;
|
||||
} on PlatformException {
|
||||
} catch (e) {
|
||||
state.value = ConnectionState.connected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on _GattViewState {
|
||||
Widget get changeStateView {
|
||||
extension on _DeviceViewState {
|
||||
Widget buildConnectionState(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: state,
|
||||
builder: (context, ConnectionState stateValue, child) {
|
||||
builder: (context, ConnectionState state, child) {
|
||||
void Function()? onPressed;
|
||||
String data;
|
||||
switch (stateValue) {
|
||||
switch (state) {
|
||||
case ConnectionState.disconnected:
|
||||
onPressed = connect;
|
||||
data = '连接';
|
||||
@ -428,7 +467,7 @@ extension on _GattViewState {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
style: TextButton.styleFrom(
|
||||
primary: Colors.white,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(data),
|
||||
);
|
||||
@ -436,19 +475,19 @@ extension on _GattViewState {
|
||||
);
|
||||
}
|
||||
|
||||
Widget get bodyView {
|
||||
Widget buildBody(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: state,
|
||||
builder: (context, ConnectionState stateValue, child) {
|
||||
switch (stateValue) {
|
||||
case ConnectionState.disconnected:
|
||||
return disconnectedView;
|
||||
return buildDisconnectedView(context);
|
||||
case ConnectionState.connecting:
|
||||
return connectingView;
|
||||
return buildConnectingView(context);
|
||||
case ConnectionState.connected:
|
||||
return connectedView;
|
||||
return buildConnectedView(context);
|
||||
case ConnectionState.disconnecting:
|
||||
return disconnectingView;
|
||||
return buildDisconnectingView(context);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@ -456,75 +495,74 @@ extension on _GattViewState {
|
||||
);
|
||||
}
|
||||
|
||||
Widget get disconnectedView {
|
||||
return Center(
|
||||
child: Text('未连接'),
|
||||
Widget buildDisconnectedView(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('Disconnected'),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get connectingView {
|
||||
return Center(
|
||||
child: Text('正在建立连接'),
|
||||
Widget buildConnectingView(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('Connecting'),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get connectedView {
|
||||
Widget buildConnectedView(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: service,
|
||||
builder: (context, GattService? serviceValue, child) {
|
||||
final services = gatt!.services.values
|
||||
.map((service) => DropdownMenuItem<GattService>(
|
||||
value: service,
|
||||
child: Text(
|
||||
service.uuid.name,
|
||||
softWrap: false,
|
||||
),
|
||||
))
|
||||
.toList();
|
||||
builder: (context, GattService? service, child) {
|
||||
final services = this.services.keys.map((service) {
|
||||
return DropdownMenuItem<GattService>(
|
||||
value: service,
|
||||
child: Text(
|
||||
service.uuid.toString(),
|
||||
softWrap: false,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
final serviceView = DropdownButton<GattService>(
|
||||
isExpanded: true,
|
||||
hint: Text('选择服务'),
|
||||
value: serviceValue,
|
||||
hint: const Text('Choose a service'),
|
||||
value: service,
|
||||
items: services,
|
||||
onChanged: (value) {
|
||||
service.value = value;
|
||||
onChanged: (service) {
|
||||
this.service.value = service;
|
||||
characteristic.value = null;
|
||||
},
|
||||
);
|
||||
final views = <Widget>[serviceView];
|
||||
if (serviceValue != null) {
|
||||
final characteristics = serviceValue.characteristics.values
|
||||
.map((characteristic) => DropdownMenuItem(
|
||||
value: characteristic,
|
||||
child: Text(
|
||||
characteristic.uuid.name,
|
||||
softWrap: false,
|
||||
),
|
||||
))
|
||||
.toList();
|
||||
if (service != null) {
|
||||
final characteristics = this.services[service]?.map((characteristic) {
|
||||
return DropdownMenuItem(
|
||||
value: characteristic,
|
||||
child: Text(
|
||||
characteristic.uuid.toString(),
|
||||
softWrap: false,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
final characteristicView = ValueListenableBuilder(
|
||||
valueListenable: characteristic,
|
||||
builder: (context, GattCharacteristic? characteristicValue, child) {
|
||||
final canWrite = characteristicValue != null &&
|
||||
(characteristicValue.canWrite ||
|
||||
characteristicValue.canWriteWithoutResponse);
|
||||
final canRead =
|
||||
characteristicValue != null && characteristicValue.canRead;
|
||||
builder: (context, GattCharacteristic? characteristic, child) {
|
||||
final canWrite = characteristic != null &&
|
||||
(characteristic.canWrite ||
|
||||
characteristic.canWriteWithoutResponse);
|
||||
final canRead = characteristic != null && characteristic.canRead;
|
||||
final canNotify =
|
||||
characteristicValue != null && characteristicValue.canNotify;
|
||||
characteristic != null && characteristic.canNotify;
|
||||
final readAndNotifyView = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: canRead
|
||||
? () async {
|
||||
final value = await characteristicValue!.read();
|
||||
final value = await characteristic.read();
|
||||
final time = DateTime.now().display;
|
||||
final log = '[$time][READ] ${hex.encode(value)}';
|
||||
logs.value = [...logs.value, log];
|
||||
}
|
||||
: null,
|
||||
icon: Icon(Icons.archive),
|
||||
icon: const Icon(Icons.archive),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: notifies,
|
||||
@ -533,19 +571,17 @@ extension on _GattViewState {
|
||||
notifiesValue,
|
||||
child) {
|
||||
final notifying =
|
||||
notifiesValue.containsKey(characteristicValue);
|
||||
notifiesValue.containsKey(characteristic);
|
||||
return IconButton(
|
||||
onPressed: canNotify
|
||||
? () async {
|
||||
if (notifying) {
|
||||
await characteristicValue!.notify(false);
|
||||
await notifiesValue
|
||||
.remove(characteristicValue)!
|
||||
.remove(characteristic)!
|
||||
.cancel();
|
||||
} else {
|
||||
await characteristicValue!.notify(true);
|
||||
notifiesValue[characteristicValue] =
|
||||
characteristicValue.valueChanged
|
||||
notifiesValue[characteristic] =
|
||||
characteristic.valueStream
|
||||
.listen((value) {
|
||||
final time = DateTime.now().display;
|
||||
final log =
|
||||
@ -567,18 +603,19 @@ extension on _GattViewState {
|
||||
final controllerView = TextField(
|
||||
controller: writeController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'MTU: ${gatt!.maximumWriteLength}',
|
||||
hintText: 'MTU: ${peripheral.maximumWriteLength}',
|
||||
suffixIcon: IconButton(
|
||||
onPressed: canWrite
|
||||
? () {
|
||||
final value = utf8.encode(writeController.text);
|
||||
final withoutResponse =
|
||||
!characteristicValue!.canWrite;
|
||||
characteristicValue.write(value,
|
||||
withoutResponse: withoutResponse);
|
||||
final elements = utf8.encode(writeController.text);
|
||||
final value = Uint8List.fromList(elements);
|
||||
characteristic.write(
|
||||
value,
|
||||
withoutResponse: !characteristic.canWrite,
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: Icon(Icons.send),
|
||||
icon: const Icon(Icons.send),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -586,10 +623,12 @@ extension on _GattViewState {
|
||||
children: [
|
||||
DropdownButton<GattCharacteristic>(
|
||||
isExpanded: true,
|
||||
hint: Text('选择特征值'),
|
||||
value: characteristicValue,
|
||||
hint: const Text('Choose a characteristic'),
|
||||
value: characteristic,
|
||||
items: characteristics,
|
||||
onChanged: (value) => characteristic.value = value,
|
||||
onChanged: (characteristic) {
|
||||
this.characteristic.value = characteristic;
|
||||
},
|
||||
),
|
||||
readAndNotifyView,
|
||||
controllerView,
|
||||
@ -617,7 +656,7 @@ extension on _GattViewState {
|
||||
);
|
||||
views.add(loggerView);
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: views,
|
||||
@ -627,9 +666,9 @@ extension on _GattViewState {
|
||||
);
|
||||
}
|
||||
|
||||
Widget get disconnectingView {
|
||||
return Center(
|
||||
child: Text('正在断开连接'),
|
||||
Widget buildDisconnectingView(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text('Disconnecting'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,41 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "47.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
version: "2.9.0"
|
||||
bluetooth_low_energy:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.0"
|
||||
version: "1.0.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -28,108 +49,164 @@ packages:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.16.0"
|
||||
convert:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: convert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_simple_treeview:
|
||||
dependency: "direct main"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_simple_treeview
|
||||
name: flutter_lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0-nullsafety.1"
|
||||
version: "2.0.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.10"
|
||||
version: "0.12.12"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.8.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
pedantic:
|
||||
dependency: "direct dev"
|
||||
version: "1.8.2"
|
||||
pigeon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pedantic
|
||||
name: pigeon
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "4.0.2"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protobuf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -141,7 +218,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -162,35 +239,56 @@ packages:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.4.12"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.12.0 <3.0.0"
|
||||
flutter: ">=1.20.0"
|
||||
dart: ">=2.17.6 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
@ -2,15 +2,22 @@ name: bluetooth_low_energy_example
|
||||
description: Demonstrates how to use the bluetooth_low_energy plugin.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `pub publish`. This is preferred for private packages.
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
sdk: ">=2.17.6 <3.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
bluetooth_low_energy:
|
||||
# When depending on this package from a real application you should use:
|
||||
# bluetooth_low_energy: ^x.y.z
|
||||
@ -18,21 +25,27 @@ dependencies:
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
convert: ^3.0.0
|
||||
flutter_simple_treeview: ^3.0.0-nullsafety.1
|
||||
convert: ^3.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
pedantic: ^1.11.0
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
@ -46,7 +59,7 @@ flutter:
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
@ -70,12 +83,3 @@ flutter:
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
|
||||
fonts:
|
||||
- family: IBM Plex Mono
|
||||
fonts:
|
||||
- asset: fonts/IBMPlexMono-Regular.ttf
|
||||
- asset: fonts/IBMPlexMono-Italic.ttf
|
||||
style: italic
|
||||
- asset: fonts/IBMPlexMono-Bold.ttf
|
||||
weight: 700
|
||||
|
@ -1,8 +1,27 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
void main() {}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:bluetooth_low_energy_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user