[Fixed] ESP32 | BLUETOOTH CLASSIC | FLUTTER - Let's build BT Serial based on the examples. (Ft. Chat App)

This commit is contained in:
Eric
2021-06-21 13:05:30 -07:00
parent af0d6b6737
commit 8655d2e7b8
14 changed files with 932 additions and 1596 deletions

View File

@@ -0,0 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_bluetooth_serial","path":"/Users/sil0/.pub-cache/hosted/pub.dartlang.org/flutter_bluetooth_serial-0.2.2/","dependencies":[]}],"android":[{"name":"flutter_bluetooth_serial","path":"/Users/sil0/.pub-cache/hosted/pub.dartlang.org/flutter_bluetooth_serial-0.2.2/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_bluetooth_serial","dependencies":[]}],"date_created":"2021-06-21 13:03:01.480634","version":"2.2.2"}

View File

@@ -1,5 +1,8 @@
### Flutter Generated
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
@@ -15,30 +18,878 @@
*.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/
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS related
**/macos/**
### https://raw.github.com/github/gitignore//Android.gitignore
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/caches
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
### https://raw.github.com/github/gitignore//Dart.gitignore
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map
### https://raw.github.com/github/gitignore//Global/JetBrains.gitignore
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### https://raw.github.com/github/gitignore//Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### https://raw.github.com/github/gitignore//Global/Xcode.gitignore
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Archives.gitignore
# It's better to unpack these files and commit the raw source because
# git has its own built in compression methods.
*.7z
*.jar
*.rar
*.zip
*.gz
*.tgz
*.bzip
*.bz2
*.xz
*.lzma
*.cab
# Packing-only formats
*.iso
*.tar
# Package management formats
*.dmg
*.xpi
*.gem
*.egg
*.deb
*.rpm
*.msi
*.msm
*.msp
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Backup.gitignore
*.bak
*.gho
*.ori
*.orig
*.tmp
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Android.gitignore
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/caches
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Dart.gitignore
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/DartEditor.gitignore
.project
.buildlog
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Emacs.gitignore
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Gradle.gitignore
.gradle
/build/
# Web related
lib/generated_plugin_registrant.dart
# Ignore Gradle GUI config
gradle-app.setting
# Symbolication related
app.*.symbols
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Obfuscation related
app.*.map.json
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Java.gitignore
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/JetBrains.gitignore
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/JEnv.gitignore
# JEnv local Java version configuration file
.java-version
# Used by previous versions of JEnv
.jenv-version
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Kotlin.gitignore
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Linux.gitignore
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Maven.gitignore
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Objective-C.gitignore
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/SublimeText.gitignore
# Cache files for Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# Workspace files are user-specific
*.sublime-workspace
# Project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using Sublime Text
# *.sublime-project
# SFTP configuration file
sftp-config.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Vim.gitignore
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/VisualStudioCode.gitignore
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Xcode.gitignore
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
### Flutter Generated Exceptions
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

@@ -1,145 +0,0 @@
import 'package:flutter/material.dart';
import './BackgroundCollectingTask.dart';
import './helpers/LineChart.dart';
import './helpers/PaintStyle.dart';
class BackgroundCollectedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final BackgroundCollectingTask task =
BackgroundCollectingTask.of(context, rebuildOnChange: true);
// Arguments shift is needed for timestamps as miliseconds in double could loose precision.
final int argumentsShift =
task.samples.first.timestamp.millisecondsSinceEpoch;
final Duration showDuration =
Duration(hours: 2); // @TODO . show duration should be configurable
final Iterable<DataSample> lastSamples = task.getLastOf(showDuration);
final Iterable<double> arguments = lastSamples.map((sample) {
return (sample.timestamp.millisecondsSinceEpoch - argumentsShift)
.toDouble();
});
// Step for argument labels
final Duration argumentsStep =
Duration(minutes: 15); // @TODO . step duration should be configurable
// Find first timestamp floored to step before
final DateTime beginningArguments = lastSamples.first.timestamp;
DateTime beginningArgumentsStep = DateTime(beginningArguments.year,
beginningArguments.month, beginningArguments.day);
while (beginningArgumentsStep.isBefore(beginningArguments)) {
beginningArgumentsStep = beginningArgumentsStep.add(argumentsStep);
}
beginningArgumentsStep = beginningArgumentsStep.subtract(argumentsStep);
final DateTime endingArguments = lastSamples.last.timestamp;
// Generate list of timestamps of labels
final Iterable<DateTime> argumentsLabelsTimestamps = () sync* {
DateTime timestamp = beginningArgumentsStep;
yield timestamp;
while (timestamp.isBefore(endingArguments)) {
timestamp = timestamp.add(argumentsStep);
yield timestamp;
}
}();
// Map strings for labels
final Iterable<LabelEntry> argumentsLabels =
argumentsLabelsTimestamps.map((timestamp) {
return LabelEntry(
(timestamp.millisecondsSinceEpoch - argumentsShift).toDouble(),
((timestamp.hour <= 9 ? '0' : '') +
timestamp.hour.toString() +
':' +
(timestamp.minute <= 9 ? '0' : '') +
timestamp.minute.toString()));
});
return Scaffold(
appBar: AppBar(
title: Text('Collected data'),
actions: <Widget>[
// Progress circle
(task.inProgress
? FittedBox(
child: Container(
margin: new EdgeInsets.all(16.0),
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white))))
: Container(/* Dummy */)),
// Start/stop buttons
(task.inProgress
? IconButton(icon: Icon(Icons.pause), onPressed: task.pause)
: IconButton(
icon: Icon(Icons.play_arrow), onPressed: task.reasume)),
],
),
body: ListView(
children: <Widget>[
Divider(),
ListTile(
leading: const Icon(Icons.brightness_7),
title: const Text('Temperatures'),
subtitle: const Text('In Celsius'),
),
LineChart(
constraints: const BoxConstraints.expand(height: 350),
arguments: arguments,
argumentsLabels: argumentsLabels,
values: [
lastSamples.map((sample) => sample.temperature1),
lastSamples.map((sample) => sample.temperature2),
],
verticalLinesStyle: const PaintStyle(color: Colors.grey),
additionalMinimalHorizontalLabelsInterval: 0,
additionalMinimalVerticalLablesInterval: 0,
seriesPointsStyles: [
null,
null,
//const PaintStyle(style: PaintingStyle.stroke, strokeWidth: 1.7*3, color: Colors.indigo, strokeCap: StrokeCap.round),
],
seriesLinesStyles: [
const PaintStyle(
style: PaintingStyle.stroke,
strokeWidth: 1.7,
color: Colors.indigoAccent),
const PaintStyle(
style: PaintingStyle.stroke,
strokeWidth: 1.7,
color: Colors.redAccent),
],
),
Divider(),
ListTile(
leading: const Icon(Icons.filter_vintage),
title: const Text('Water pH level'),
),
LineChart(
constraints: const BoxConstraints.expand(height: 200),
arguments: arguments,
argumentsLabels: argumentsLabels,
values: [
lastSamples.map((sample) => sample.waterpHlevel),
],
verticalLinesStyle: const PaintStyle(color: Colors.grey),
additionalMinimalHorizontalLabelsInterval: 0,
additionalMinimalVerticalLablesInterval: 0,
seriesPointsStyles: [
null,
],
seriesLinesStyles: [
const PaintStyle(
style: PaintingStyle.stroke,
strokeWidth: 1.7,
color: Colors.greenAccent),
],
),
],
));
}
}

View File

@@ -1,123 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:scoped_model/scoped_model.dart';
class DataSample {
double temperature1;
double temperature2;
double waterpHlevel;
DateTime timestamp;
DataSample({
this.temperature1,
this.temperature2,
this.waterpHlevel,
this.timestamp,
});
}
class BackgroundCollectingTask extends Model {
static BackgroundCollectingTask of(
BuildContext context, {
bool rebuildOnChange = false,
}) =>
ScopedModel.of<BackgroundCollectingTask>(
context,
rebuildOnChange: rebuildOnChange,
);
final BluetoothConnection _connection;
List<int> _buffer = List<int>();
// @TODO , Such sample collection in real code should be delegated
// (via `Stream<DataSample>` preferably) and then saved for later
// displaying on chart (or even stright prepare for displaying).
// @TODO ? should be shrinked at some point, endless colleting data would cause memory shortage.
List<DataSample> samples = List<DataSample>();
bool inProgress;
BackgroundCollectingTask._fromConnection(this._connection) {
_connection.input.listen((data) {
_buffer += data;
while (true) {
// If there is a sample, and it is full sent
int index = _buffer.indexOf('t'.codeUnitAt(0));
if (index >= 0 && _buffer.length - index >= 7) {
final DataSample sample = DataSample(
temperature1: (_buffer[index + 1] + _buffer[index + 2] / 100),
temperature2: (_buffer[index + 3] + _buffer[index + 4] / 100),
waterpHlevel: (_buffer[index + 5] + _buffer[index + 6] / 100),
timestamp: DateTime.now());
_buffer.removeRange(0, index + 7);
samples.add(sample);
notifyListeners(); // Note: It shouldn't be invoked very often - in this example data comes at every second, but if there would be more data, it should update (including repaint of graphs) in some fixed interval instead of after every sample.
//print("${sample.timestamp.toString()} -> ${sample.temperature1} / ${sample.temperature2}");
}
// Otherwise break
else {
break;
}
}
}).onDone(() {
inProgress = false;
notifyListeners();
});
}
static Future<BackgroundCollectingTask> connect(
BluetoothDevice server) async {
final BluetoothConnection connection =
await BluetoothConnection.toAddress(server.address);
return BackgroundCollectingTask._fromConnection(connection);
}
void dispose() {
_connection.dispose();
}
Future<void> start() async {
inProgress = true;
_buffer.clear();
samples.clear();
notifyListeners();
_connection.output.add(ascii.encode('start'));
await _connection.output.allSent;
}
Future<void> cancel() async {
inProgress = false;
notifyListeners();
_connection.output.add(ascii.encode('stop'));
await _connection.finish();
}
Future<void> pause() async {
inProgress = false;
notifyListeners();
_connection.output.add(ascii.encode('stop'));
await _connection.output.allSent;
}
Future<void> reasume() async {
inProgress = true;
notifyListeners();
_connection.output.add(ascii.encode('start'));
await _connection.output.allSent;
}
Iterable<DataSample> getLastOf(Duration duration) {
DateTime startingTime = DateTime.now().subtract(duration);
int i = samples.length;
do {
i -= 1;
if (i <= 0) {
break;
}
} while (samples[i].timestamp.isAfter(startingTime));
return samples.getRange(i, samples.length);
}
}

View File

@@ -3,17 +3,15 @@ import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
class BluetoothDeviceListEntry extends ListTile {
BluetoothDeviceListEntry({
@required BluetoothDevice device,
int rssi,
GestureTapCallback onTap,
GestureLongPressCallback onLongPress,
required BluetoothDevice device,
required rssi,
required GestureTapCallback onTap,
bool enabled = true,
}) : super(
onTap: onTap,
onLongPress: onLongPress,
enabled: enabled,
leading:
Icon(Icons.devices), // @TODO . !BluetoothClass! class aware icon
leading: Icon(Icons.devices),
// @TODO . !BluetoothClass! class aware icon
title: Text(device.name ?? "Unknown device"),
subtitle: Text(device.address.toString()),
trailing: Row(

View File

@@ -1,13 +1,14 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
class ChatPage extends StatefulWidget {
final BluetoothDevice server;
const ChatPage({this.server});
const ChatPage({required this.server});
@override
_ChatPage createState() => new _ChatPage();
@@ -22,9 +23,9 @@ class _Message {
class _ChatPage extends State<ChatPage> {
static final clientID = 0;
BluetoothConnection connection;
var connection; //BluetoothConnection
List<_Message> messages = List<_Message>();
List<_Message> messages = [];
String _messageBuffer = '';
final TextEditingController textEditingController =
@@ -32,8 +33,6 @@ class _ChatPage extends State<ChatPage> {
final ScrollController listScrollController = new ScrollController();
bool isConnecting = true;
bool get isConnected => connection != null && connection.isConnected;
bool isDisconnecting = false;
@override
@@ -73,7 +72,7 @@ class _ChatPage extends State<ChatPage> {
@override
void dispose() {
// Avoid memory leak (`setState` after dispose) and disconnect
if (isConnected) {
if (isConnected()) {
isDisconnecting = true;
connection.dispose();
connection = null;
@@ -112,7 +111,7 @@ class _ChatPage extends State<ChatPage> {
appBar: AppBar(
title: (isConnecting
? Text('Connecting chat to ' + widget.server.name + '...')
: isConnected
: isConnected()
? Text('Live chat with ' + widget.server.name)
: Text('Chat log with ' + widget.server.name))),
body: SafeArea(
@@ -135,12 +134,12 @@ class _ChatPage extends State<ChatPage> {
decoration: InputDecoration.collapsed(
hintText: isConnecting
? 'Wait until connected...'
: isConnected
: isConnected()
? 'Type your message...'
: 'Chat got disconnected',
hintStyle: const TextStyle(color: Colors.grey),
),
enabled: isConnected,
enabled: isConnected(),
),
),
),
@@ -148,7 +147,7 @@ class _ChatPage extends State<ChatPage> {
margin: const EdgeInsets.all(8.0),
child: IconButton(
icon: const Icon(Icons.send),
onPressed: isConnected
onPressed: isConnected()
? () => _sendMessage(textEditingController.text)
: null),
),
@@ -234,4 +233,8 @@ class _ChatPage extends State<ChatPage> {
}
}
}
bool isConnected() {
return connection != null && connection.isConnected;
}
}

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
@@ -15,11 +16,9 @@ class DiscoveryPage extends StatefulWidget {
}
class _DiscoveryPage extends State<DiscoveryPage> {
StreamSubscription<BluetoothDiscoveryResult> _streamSubscription;
List<BluetoothDiscoveryResult> results = List<BluetoothDiscoveryResult>();
bool isDiscovering;
_DiscoveryPage();
late StreamSubscription<BluetoothDiscoveryResult> _streamSubscription;
List<BluetoothDiscoveryResult> results = [];
late bool isDiscovering;
@override
void initState() {
@@ -98,53 +97,6 @@ class _DiscoveryPage extends State<DiscoveryPage> {
onTap: () {
Navigator.of(context).pop(result.device);
},
onLongPress: () async {
try {
bool bonded = false;
if (result.device.isBonded) {
print('Unbonding from ${result.device.address}...');
await FlutterBluetoothSerial.instance
.removeDeviceBondWithAddress(result.device.address);
print('Unbonding from ${result.device.address} has succed');
} else {
print('Bonding with ${result.device.address}...');
bonded = await FlutterBluetoothSerial.instance
.bondDeviceAtAddress(result.device.address);
print(
'Bonding with ${result.device.address} has ${bonded ? 'succed' : 'failed'}.');
}
setState(() {
results[results.indexOf(result)] = BluetoothDiscoveryResult(
device: BluetoothDevice(
name: result.device.name ?? '',
address: result.device.address,
type: result.device.type,
bondState: bonded
? BluetoothBondState.bonded
: BluetoothBondState.none,
),
rssi: result.rssi);
});
} catch (ex) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error occured while bonding'),
content: Text("${ex.toString()}"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
);
},
),

View File

@@ -1,15 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:scoped_model/scoped_model.dart';
import './DiscoveryPage.dart';
import './SelectBondedDevicePage.dart';
import './ChatPage.dart';
import './BackgroundCollectingTask.dart';
import './BackgroundCollectedPage.dart';
// import './helpers/LineChart.dart';
import './DiscoveryPage.dart';
class MainPage extends StatefulWidget {
@override
@@ -22,13 +17,6 @@ class _MainPage extends State<MainPage> {
String _address = "...";
String _name = "...";
Timer _discoverableTimeoutTimer;
int _discoverableTimeoutSecondsLeft = 0;
BackgroundCollectingTask _collectingTask;
bool _autoAcceptPairingRequests = false;
@override
void initState() {
super.initState();
@@ -68,10 +56,6 @@ class _MainPage extends State<MainPage> {
.listen((BluetoothState state) {
setState(() {
_bluetoothState = state;
// Discoverable mode is disabled when Bluetooth gets disabled
_discoverableTimeoutTimer = null;
_discoverableTimeoutSecondsLeft = 0;
});
});
}
@@ -79,8 +63,6 @@ class _MainPage extends State<MainPage> {
@override
void dispose() {
FlutterBluetoothSerial.instance.setPairingRequestHandler(null);
_collectingTask?.dispose();
_discoverableTimeoutTimer?.cancel();
super.dispose();
}
@@ -132,91 +114,11 @@ class _MainPage extends State<MainPage> {
subtitle: Text(_name),
onLongPress: null,
),
ListTile(
title: _discoverableTimeoutSecondsLeft == 0
? const Text("Discoverable")
: Text(
"Discoverable for ${_discoverableTimeoutSecondsLeft}s"),
subtitle: const Text("PsychoX-Luna"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(
value: _discoverableTimeoutSecondsLeft != 0,
onChanged: null,
),
IconButton(
icon: const Icon(Icons.edit),
onPressed: null,
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () async {
print('Discoverable requested');
final int timeout = await FlutterBluetoothSerial.instance
.requestDiscoverable(60);
if (timeout < 0) {
print('Discoverable mode denied');
} else {
print(
'Discoverable mode acquired for $timeout seconds');
}
setState(() {
_discoverableTimeoutTimer?.cancel();
_discoverableTimeoutSecondsLeft = timeout;
_discoverableTimeoutTimer =
Timer.periodic(Duration(seconds: 1), (Timer timer) {
setState(() {
if (_discoverableTimeoutSecondsLeft < 0) {
FlutterBluetoothSerial.instance.isDiscoverable
.then((isDiscoverable) {
if (isDiscoverable) {
print(
"Discoverable after timeout... might be infinity timeout :F");
_discoverableTimeoutSecondsLeft += 1;
}
});
timer.cancel();
_discoverableTimeoutSecondsLeft = 0;
} else {
_discoverableTimeoutSecondsLeft -= 1;
}
});
});
});
},
)
],
),
),
Divider(),
ListTile(title: const Text('Devices discovery and connection')),
SwitchListTile(
title: const Text('Auto-try specific pin when pairing'),
subtitle: const Text('Pin 1234'),
value: _autoAcceptPairingRequests,
onChanged: (bool value) {
setState(() {
_autoAcceptPairingRequests = value;
});
if (value) {
FlutterBluetoothSerial.instance.setPairingRequestHandler(
(BluetoothPairingRequest request) {
print("Trying to auto-pair with Pin 1234");
if (request.pairingVariant == PairingVariant.Pin) {
return Future.value("1234");
}
return null;
});
} else {
FlutterBluetoothSerial.instance
.setPairingRequestHandler(null);
}
},
),
ListTile(
title: RaisedButton(
child: const Text('Explore discovered devices'),
title: TextButton(
child:
const Text('Connect to paired device to chat with ESP32'),
onPressed: () async {
final BluetoothDevice selectedDevice =
await Navigator.of(context).push(
@@ -229,86 +131,12 @@ class _MainPage extends State<MainPage> {
if (selectedDevice != null) {
print('Discovery -> selected ' + selectedDevice.address);
_startChat(context, selectedDevice);
} else {
print('Discovery -> no device selected');
}
}),
),
ListTile(
title: RaisedButton(
child: const Text('Connect to paired device to chat'),
onPressed: () async {
final BluetoothDevice selectedDevice =
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return SelectBondedDevicePage(checkAvailability: false);
},
),
);
if (selectedDevice != null) {
print('Connect -> selected ' + selectedDevice.address);
_startChat(context, selectedDevice);
} else {
print('Connect -> no device selected');
}
},
),
),
Divider(),
ListTile(title: const Text('Multiple connections example')),
ListTile(
title: RaisedButton(
child: ((_collectingTask != null && _collectingTask.inProgress)
? const Text('Disconnect and stop background collecting')
: const Text('Connect to start background collecting')),
onPressed: () async {
if (_collectingTask != null && _collectingTask.inProgress) {
await _collectingTask.cancel();
setState(() {
/* Update for `_collectingTask.inProgress` */
});
} else {
final BluetoothDevice selectedDevice =
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return SelectBondedDevicePage(
checkAvailability: false);
},
),
);
if (selectedDevice != null) {
await _startBackgroundTask(context, selectedDevice);
setState(() {
/* Update for `_collectingTask.inProgress` */
});
}
}
},
),
),
ListTile(
title: RaisedButton(
child: const Text('View background collected data'),
onPressed: (_collectingTask != null)
? () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return ScopedModel<BackgroundCollectingTask>(
model: _collectingTask,
child: BackgroundCollectedPage(),
);
},
),
);
}
: null,
),
),
],
),
),
@@ -324,35 +152,4 @@ class _MainPage extends State<MainPage> {
),
);
}
Future<void> _startBackgroundTask(
BuildContext context,
BluetoothDevice server,
) async {
try {
_collectingTask = await BackgroundCollectingTask.connect(server);
await _collectingTask.start();
} catch (ex) {
if (_collectingTask != null) {
_collectingTask.cancel();
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error occured while connecting'),
content: Text("${ex.toString()}"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
}

View File

@@ -1,144 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import './BluetoothDeviceListEntry.dart';
class SelectBondedDevicePage extends StatefulWidget {
/// If true, on page start there is performed discovery upon the bonded devices.
/// Then, if they are not avaliable, they would be disabled from the selection.
final bool checkAvailability;
const SelectBondedDevicePage({this.checkAvailability = true});
@override
_SelectBondedDevicePage createState() => new _SelectBondedDevicePage();
}
enum _DeviceAvailability {
no,
maybe,
yes,
}
class _DeviceWithAvailability extends BluetoothDevice {
BluetoothDevice device;
_DeviceAvailability availability;
int rssi;
_DeviceWithAvailability(this.device, this.availability, [this.rssi]);
}
class _SelectBondedDevicePage extends State<SelectBondedDevicePage> {
List<_DeviceWithAvailability> devices = List<_DeviceWithAvailability>();
// Availability
StreamSubscription<BluetoothDiscoveryResult> _discoveryStreamSubscription;
bool _isDiscovering;
_SelectBondedDevicePage();
@override
void initState() {
super.initState();
_isDiscovering = widget.checkAvailability;
if (_isDiscovering) {
_startDiscovery();
}
// Setup a list of the bonded devices
FlutterBluetoothSerial.instance
.getBondedDevices()
.then((List<BluetoothDevice> bondedDevices) {
setState(() {
devices = bondedDevices
.map(
(device) => _DeviceWithAvailability(
device,
widget.checkAvailability
? _DeviceAvailability.maybe
: _DeviceAvailability.yes,
),
)
.toList();
});
});
}
void _restartDiscovery() {
setState(() {
_isDiscovering = true;
});
_startDiscovery();
}
void _startDiscovery() {
_discoveryStreamSubscription =
FlutterBluetoothSerial.instance.startDiscovery().listen((r) {
setState(() {
Iterator i = devices.iterator;
while (i.moveNext()) {
var _device = i.current;
if (_device.device == r.device) {
_device.availability = _DeviceAvailability.yes;
_device.rssi = r.rssi;
}
}
});
});
_discoveryStreamSubscription.onDone(() {
setState(() {
_isDiscovering = false;
});
});
}
@override
void dispose() {
// Avoid memory leak (`setState` after dispose) and cancel discovery
_discoveryStreamSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
List<BluetoothDeviceListEntry> list = devices
.map((_device) => BluetoothDeviceListEntry(
device: _device.device,
rssi: _device.rssi,
enabled: _device.availability == _DeviceAvailability.yes,
onTap: () {
Navigator.of(context).pop(_device.device);
},
))
.toList();
return Scaffold(
appBar: AppBar(
title: Text('Select device'),
actions: <Widget>[
_isDiscovering
? FittedBox(
child: Container(
margin: new EdgeInsets.all(16.0),
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
),
)
: IconButton(
icon: Icon(Icons.replay),
onPressed: _restartDiscovery,
)
],
),
body: ListView(children: list),
);
}
}

View File

@@ -1,600 +0,0 @@
/// @name LineChart
/// @version 0.0.5
/// @description Simple line chart widget
/// @author Patryk "PsychoX" Ludwikowski <patryk.ludwikowski.7+dart@gmail.com>
/// @license MIT License (see https://mit-license.org/)
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:math' as math show min, max;
import './PaintStyle.dart';
class LabelEntry {
final double value;
final String label;
LabelEntry(this.value, this.label);
}
/// Widget that allows to show data on line chart.
///
/// All arguments, values and labels data should be sorted!
/// Since both the arguments and the values must be `double` type,
/// be aware of the precision.
class LineChart extends StatelessWidget {
/// Constraints for the line chart.
final BoxConstraints constraints;
// @TODO ? Both `_LineChartPainter` and `LineChart` have most the same fields.
// `LineChart` is just mainly passing them to the painter. Shouldn't there be
// only one class containing these data? Some `LineChartData` forged inside here
// and then passed and used by the painter? :thinking:
/// Padding around main drawng area. Necessary for displaying labels (around the chart).
final EdgeInsets padding;
/* Arguments */
/// Collection of doubles as arguments.
final Iterable<double> arguments;
/// Mappings of strings for doubles arguments, which allow to specify custom
/// strings as labels for certain arguments.
final Iterable<LabelEntry> argumentsLabels;
/* Values */
/// Collection of data series as collections of next values on corresponding arguments.
final Iterable<Iterable<double>> values;
/// Mappings of string for doubles values, which allow to specify custom
/// string as labels for certain values.
final Iterable<LabelEntry> valuesLabels;
/* Labels & lines styles */
/// Style of horizontal lines labels
final TextStyle horizontalLabelsTextStyle;
/// Style of vertical lines labels
final TextStyle verticalLabelsTextStyle;
/// Defines style of horizontal lines. Might be null in order to prevent lines from drawing.
final Paint horizontalLinesPaint;
/// Defines style of vertical lines. Might be null in order to prevent lines from drawing.
final Paint verticalLinesPaint;
// @TODO . expose it
final bool snapToLeftLabel = false;
final bool snapToTopLabel = true;
final bool snapToRightLabel = false;
final bool snapToBottomLabel = true;
/* Series points & lines styles */
/// List of paint styles for series values points.
///
/// On whole list null would use predefined set of styles.
/// On list entry null there will be no points for certain series.
final List<Paint> seriesPointsPaints;
/// List of paint styles for lines between next series points.
///
/// On null there will be no lines.
final List<Paint> seriesLinesPaints;
final double additionalMinimalHorizontalLabelsInterval;
final double additionalMinimalVerticalLablesInterval;
LineChart({
@required this.constraints,
this.padding = const EdgeInsets.fromLTRB(32, 12, 20, 28),
this.arguments,
this.argumentsLabels,
this.values,
this.valuesLabels,
this.horizontalLabelsTextStyle,
this.verticalLabelsTextStyle,
PaintStyle horizontalLinesStyle = const PaintStyle(color: Colors.grey),
PaintStyle verticalLinesStyle, // null for default
this.additionalMinimalHorizontalLabelsInterval = 8,
this.additionalMinimalVerticalLablesInterval = 8,
Iterable<PaintStyle>
seriesPointsStyles, // null would use predefined set of styles
Iterable<PaintStyle> seriesLinesStyles, // null for default
}) : horizontalLinesPaint = horizontalLinesStyle?.toPaint(),
verticalLinesPaint = verticalLinesStyle?.toPaint(),
seriesPointsPaints = _prepareSeriesPointsPaints(seriesPointsStyles),
seriesLinesPaints = _prepareSeriesLinesPaints(seriesLinesStyles) {
if (seriesPointsStyles.length < values.length &&
12 /* default paints */ < values.length) {
throw "Too few `seriesPointsPaintStyle`s! Try define more or limit number of displayed series";
}
if (seriesLinesStyles.length < values.length) {
throw "Too few `seriesLinesStyles`s! Try define more or limit number of displayed series";
}
}
static Iterable<Paint> _prepareSeriesPointsPaints(
Iterable<PaintStyle> seriesPointsStyles) {
if (seriesPointsStyles == null) {
// Default paint for points
return List<Paint>.unmodifiable(<Paint>[
PaintStyle(strokeWidth: 1.7, color: Colors.blue).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.red).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.yellow).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.green).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.purple).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.deepOrange).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.brown).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.lime).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.indigo).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.pink).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.amber).toPaint(),
PaintStyle(strokeWidth: 1.7, color: Colors.teal).toPaint(),
// For more, user should specify them :F
]);
} else {
return seriesPointsStyles.map((style) => style?.toPaint()).toList();
}
}
static Iterable<Paint> _prepareSeriesLinesPaints(
Iterable<PaintStyle> seriesLinesStyles) {
if (seriesLinesStyles == null) {
return null;
} else {
return seriesLinesStyles.map((style) => style.toPaint()).toList();
}
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: this.constraints,
child: CustomPaint(
painter: _LineChartPainter(
padding: padding,
arguments: arguments,
argumentsLabels: argumentsLabels,
values: values,
valuesLabels: valuesLabels,
horizontalLabelsTextStyle:
horizontalLabelsTextStyle ?? Theme.of(context).textTheme.caption,
verticalLabelsTextStyle:
verticalLabelsTextStyle ?? Theme.of(context).textTheme.caption,
horizontalLinesPaint: horizontalLinesPaint,
verticalLinesPaint: verticalLinesPaint,
additionalMinimalHorizontalLabelsInterval:
additionalMinimalHorizontalLabelsInterval,
additionalMinimalVerticalLablesInterval:
additionalMinimalVerticalLablesInterval,
seriesPointsPaints: seriesPointsPaints,
seriesLinesPaints: seriesLinesPaints,
)));
}
}
class _LineChartPainter extends CustomPainter {
/// Padding around main drawng area. Necessary for displaying labels (around the chart).
final EdgeInsets padding;
/* Arguments */
/// Collection of doubles as arguments.
final Iterable<double> arguments;
/// Mappings of strings for doubles arguments, which allow to specify custom
/// strings as labels for certain arguments.
final Iterable<LabelEntry> argumentsLabels;
/* Values */
/// Collection of data series as collections of next values on corresponding arguments.
final Iterable<Iterable<double>> values;
/// Mappings of string for doubles values, which allow to specify custom
/// string as labels for certain values.
final Iterable<LabelEntry> valuesLabels;
/* Labels & lines styles */
/// Style of horizontal lines labels
final TextStyle horizontalLabelsTextStyle;
/// Style of vertical lines labels
final TextStyle verticalLabelsTextStyle;
/// Defines style of horizontal lines. Might be null in order to prevent lines from drawing.
final Paint horizontalLinesPaint;
/// Defines style of vertical lines. Might be null in order to prevent lines from drawing.
final Paint verticalLinesPaint;
// @TODO . expose it
final bool snapToLeftLabel = false;
final bool snapToTopLabel = true;
final bool snapToRightLabel = false;
final bool snapToBottomLabel = true;
/* Series points & lines styles */
/// Collection of paint styles for series values points.
///
/// On whole argument null would use predefined set of styles.
/// On collection entry null there will be no points for certain series.
final Iterable<Paint> seriesPointsPaints;
/// Collection of paint styles for lines between next series points.
///
/// On null there will be no lines.
final Iterable<Paint> seriesLinesPaints;
/* Runtime */
/// Minimal allowed interval between horizontal lines. Calculated from font size.
final double minimalHorizontalLabelsInterval;
/// Maximal value of all data series values
double maxValue = -double.maxFinite;
/// Minimal value of all data series values
double minValue = double.maxFinite;
double _minimalHorizontalRatio = 0;
double _minimalVerticalRatio = 0;
/// Creates `_LineChartPainter` (`CustomPainter`) with given data and styling.
_LineChartPainter({
this.padding = const EdgeInsets.fromLTRB(40, 8, 8, 32),
this.arguments,
this.argumentsLabels,
this.values,
this.valuesLabels,
@required this.horizontalLabelsTextStyle,
@required this.verticalLabelsTextStyle,
@required this.horizontalLinesPaint,
@required this.verticalLinesPaint,
double additionalMinimalHorizontalLabelsInterval = 8,
double additionalMinimalVerticalLablesInterval = 8,
@required this.seriesPointsPaints,
@required this.seriesLinesPaints,
}) : this.minimalHorizontalLabelsInterval =
horizontalLabelsTextStyle.fontSize +
additionalMinimalHorizontalLabelsInterval {
// Find max & min values of data to be show
for (Iterable<double> series in values) {
for (double value in series) {
if (value > maxValue) {
maxValue = value;
} else if (value < minValue) {
minValue = value;
}
}
}
if (valuesLabels != null) {
// Find minimal vertical ratio to fit all provided values labels
Iterator<LabelEntry> entry = valuesLabels.iterator;
entry.moveNext();
double lastValue = entry.current.value;
while (entry.moveNext()) {
final double goodRatio =
minimalHorizontalLabelsInterval / (entry.current.value - lastValue);
if (goodRatio > _minimalVerticalRatio) {
_minimalVerticalRatio = goodRatio;
}
lastValue = entry.current.value;
}
}
if (argumentsLabels != null) {
// Find minimal horizontal ratio to fit all provided arguments labels
Iterator<LabelEntry> entry = argumentsLabels.iterator;
entry.moveNext();
double lastValue = entry.current.value;
double lastWidth =
_getLabelTextPainter(entry.current.label, verticalLabelsTextStyle)
.width;
while (entry.moveNext()) {
final double nextValue = entry.current.value;
final double nextWidth =
_getLabelTextPainter(entry.current.label, verticalLabelsTextStyle)
.width;
final double goodRatio = ((lastWidth + nextWidth) / 2 +
additionalMinimalVerticalLablesInterval) /
(nextValue - lastValue);
if (goodRatio > _minimalHorizontalRatio) {
_minimalHorizontalRatio = goodRatio;
}
lastValue = nextValue;
lastWidth = nextWidth;
}
}
}
@override
void paint(Canvas canvas, Size size) {
final double width = size.width - padding.left - padding.right;
final double height = size.height - padding.top - padding.bottom;
/* Horizontal lines with labels */
double valuesOffset = 0; // @TODO ? could be used in future for scrolling
double verticalRatio;
{
Iterable<LabelEntry> labels;
// If no labels provided - generate them!
if (valuesLabels == null) {
final double optimalStepValue =
_calculateOptimalStepValue(maxValue - minValue, height);
int stepsNumber = 1;
// Find bottom line value
double bottomValue = 0;
if (minValue > 0) {
while (bottomValue < minValue) {
bottomValue += optimalStepValue;
}
bottomValue -= optimalStepValue;
} else {
while (bottomValue > minValue) {
bottomValue -= optimalStepValue;
}
}
valuesOffset = bottomValue;
// Find top line value
double topValue = bottomValue;
while (topValue < maxValue) {
topValue += optimalStepValue;
stepsNumber += 1;
}
// Set labels iterable from prepared generator
Iterable<LabelEntry> generator(double optimalStepValue, int stepsNumber,
[double value = 0.0]) sync* {
//double value = _bottomValue;
for (int i = 0; i < stepsNumber; i++) {
yield LabelEntry(
value,
value
.toString()); // @TODO , choose better precision based on optimal step value while parsing to string
value += optimalStepValue;
}
}
labels = generator(optimalStepValue, stepsNumber, bottomValue);
if (!snapToTopLabel) {
topValue = maxValue;
}
if (!snapToBottomLabel) {
bottomValue = valuesOffset = minValue;
}
// Calculate vertical ratio of pixels per value
// Note: There is no empty space already
verticalRatio = height / (topValue - bottomValue);
}
// If labels provided - use them
else {
// Set labels iterable as the provided list
labels = valuesLabels;
// Use minimal visible value as offset.
// Note: `minValue` is calculated in constructor and includes miniaml labels values.
valuesOffset = minValue;
// Calculate vertical ratio of pixels per value
// Note: `_minimalVerticalRatio` is calculated in constructor
final double topValue = snapToTopLabel
? math.max(maxValue, valuesLabels.last.value)
: maxValue;
final double bottomValue = snapToBottomLabel
? math.min(minValue, valuesLabels.first.value)
: minValue;
final double noEmptySpaceRatio = height / (topValue - bottomValue);
verticalRatio = math.max(_minimalVerticalRatio, noEmptySpaceRatio);
}
// Draw the horizontal lines and labels
for (LabelEntry tuple in labels) {
if (tuple.value < valuesOffset) continue;
final double yOffset = (size.height -
padding.bottom -
(tuple.value - valuesOffset) * verticalRatio);
if (yOffset < padding.top) break;
// Draw line
if (horizontalLinesPaint != null) {
canvas.drawLine(
Offset(padding.left, yOffset),
Offset(size.width - padding.right, yOffset),
horizontalLinesPaint);
}
// Draw label
TextPainter(
text: TextSpan(text: tuple.label, style: horizontalLabelsTextStyle),
textAlign: TextAlign.right,
textDirection: TextDirection.ltr)
..layout(minWidth: padding.left - 4)
..paint(canvas,
Offset(0, yOffset - horizontalLabelsTextStyle.fontSize / 2 - 1));
}
}
/* Vertical lines with labels */
double argumentsOffset = 0;
final double xOffsetLimit = size.width - padding.right;
double horizontalRatio;
{
Iterable<LabelEntry> labels;
// If no labels provided - generate them!
if (argumentsLabels == null) {
throw "not implemented";
// @TODO . after few hot days of thinking about the problem for 1-2 hour a day, I just gave up.
// The hardest in the problem is that there must be trade-off between space for labels and max lines,
// but keep in mind that the label values should be in some human-readable steps (0.5, 10, 0.02...).
}
// If labels provided - use them
else {
// Set labels iterable as the provided list
labels = argumentsLabels;
// Use first visible argument as arguments offset
argumentsOffset = argumentsLabels.first.value;
if (!snapToLeftLabel) {
argumentsOffset = arguments.first;
}
// Calculate vertical ratio of pixels per value
// Note: `_minimalHorizontalRatio` is calculated in constructor
final double leftMost = snapToLeftLabel
? math.min(arguments.first, argumentsLabels.first.value)
: arguments.first;
final double rightMost = snapToRightLabel
? math.max(arguments.last, argumentsLabels.last.value)
: arguments.last;
final double noEmptySpaceRatio = width / (rightMost - leftMost);
horizontalRatio = math.max(_minimalHorizontalRatio, noEmptySpaceRatio);
}
// Draw the vertical lines and labels
for (LabelEntry tuple in labels) {
if (tuple.value < argumentsOffset) continue;
final double xOffset =
padding.left + (tuple.value - argumentsOffset) * horizontalRatio;
if (xOffset > xOffsetLimit) break;
// Draw line
if (verticalLinesPaint != null) {
canvas.drawLine(
Offset(xOffset, padding.top),
Offset(xOffset, size.height - padding.bottom),
verticalLinesPaint);
}
// Draw label
final TextPainter textPainter = TextPainter(
text: TextSpan(text: tuple.label, style: verticalLabelsTextStyle),
textDirection: TextDirection.ltr)
..layout();
textPainter.paint(
canvas,
Offset(xOffset - textPainter.width / 2,
size.height - verticalLabelsTextStyle.fontSize - 8));
}
}
/* Points and lines between subsequent */
Iterator<Iterable<double>> series = values.iterator;
Iterator<Paint> linesPaints = seriesLinesPaints == null
? <Paint>[].iterator
: seriesLinesPaints.iterator;
Iterator<Paint> pointsPaints = seriesPointsPaints.iterator;
while (series.moveNext()) {
List<Offset> points = [];
Iterator<double> value = series.current.iterator;
Iterator<double> argument = arguments.iterator;
while (value.moveNext()) {
argument.moveNext();
if (value.current == null || value.current == double.nan) continue;
if (argument.current < argumentsOffset) continue;
final double xOffset = padding.left +
(argument.current - argumentsOffset) * horizontalRatio;
if (xOffset > xOffsetLimit) break;
if (value.current < valuesOffset) continue;
final double yOffset = size.height -
padding.bottom -
(value.current - valuesOffset) * verticalRatio;
if (yOffset < padding.top) continue;
points.add(Offset(xOffset, yOffset));
}
// Lines
if (linesPaints.moveNext() && linesPaints.current != null) {
canvas.drawPath(Path()..addPolygon(points, false), linesPaints.current);
}
// Points
if (pointsPaints.moveNext() && pointsPaints.current != null) {
canvas.drawPoints(ui.PointMode.points, points, pointsPaints.current);
}
}
}
@override
bool shouldRepaint(_LineChartPainter old) =>
(this.arguments != old.arguments ||
this.values != old.values ||
this.argumentsLabels != old.argumentsLabels ||
this.valuesLabels != old.valuesLabels ||
this.seriesPointsPaints != old.seriesPointsPaints ||
this.seriesLinesPaints != old.seriesLinesPaints ||
this.horizontalLabelsTextStyle != old.horizontalLabelsTextStyle ||
this.verticalLabelsTextStyle != old.verticalLabelsTextStyle ||
this.padding != old.padding //
);
// ..., 0.01, 0.02, 0.05, 0.1, [0.125], 0.2, [0.25], 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, ...
double _calculateOptimalStepValue(double valueRange, double height) {
final int maxSteps = height ~/ minimalHorizontalLabelsInterval;
if (maxSteps <= 0) {
throw "invalid max lines!";
}
double interval = valueRange / maxSteps;
if (interval > 1) {
int zeros = 0;
while (interval >= 10) {
interval = interval / 10;
zeros += 1;
}
/**/ if (interval <= 1) {
interval = 1;
} else if (interval <= 2) {
interval = 2;
} else if (interval <= 5) {
interval = 5;
}
for (; zeros-- != 0;) {
interval *= 10;
}
} else {
// @TODO ! not working at all for lower :C
int zeros = 0;
while (interval < 0) {
interval = interval * 10;
zeros += 1;
}
/**/ if (interval <= 1) {
interval = 1;
} else if (interval <= 2) {
interval = 2;
} else if (interval <= 5) {
interval = 5;
}
for (; zeros-- != 0;) {
interval /= 10;
}
}
return interval;
}
TextPainter _getLabelTextPainter(String text, TextStyle style) {
return TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr)
..layout();
}
}

View File

@@ -1,263 +0,0 @@
import 'dart:ui';
/// A description of the style to use when drawing on a [Canvas].
///
/// Most APIs on [Canvas] take a [Paint] object to describe the style
/// to use for that operation. [PaintStyle] allows to be const
/// constructed and later in runtime forged into the [Paint] object.
class PaintStyle {
/// Whether to apply anti-aliasing to lines and images drawn on the
/// canvas.
///
/// Defaults to true.
final bool isAntiAlias;
// Must be kept in sync with the default in paint.cc.
static const int _kColorDefault = 0xFF000000;
/// The color to use when stroking or filling a shape.
///
/// Defaults to opaque black.
///
/// See also:
///
/// * [style], which controls whether to stroke or fill (or both).
/// * [colorFilter], which overrides [color].
/// * [shader], which overrides [color] with more elaborate effects.
///
/// This color is not used when compositing. To colorize a layer, use
/// [colorFilter].
final Color color;
// Must be kept in sync with the default in paint.cc.
static final int _kBlendModeDefault = BlendMode.srcOver.index;
/// A blend mode to apply when a shape is drawn or a layer is composited.
///
/// The source colors are from the shape being drawn (e.g. from
/// [Canvas.drawPath]) or layer being composited (the graphics that were drawn
/// between the [Canvas.saveLayer] and [Canvas.restore] calls), after applying
/// the [colorFilter], if any.
///
/// The destination colors are from the background onto which the shape or
/// layer is being composited.
///
/// Defaults to [BlendMode.srcOver].
///
/// See also:
///
/// * [Canvas.saveLayer], which uses its [Paint]'s [blendMode] to composite
/// the layer when [restore] is called.
/// * [BlendMode], which discusses the user of [saveLayer] with [blendMode].
final BlendMode blendMode;
/// Whether to paint inside shapes, the edges of shapes, or both.
///
/// Defaults to [PaintingStyle.fill].
final PaintingStyle style;
/// How wide to make edges drawn when [style] is set to
/// [PaintingStyle.stroke]. The width is given in logical pixels measured in
/// the direction orthogonal to the direction of the path.
///
/// Defaults to 0.0, which correspond to a hairline width.
final double strokeWidth;
/// The kind of finish to place on the end of lines drawn when
/// [style] is set to [PaintingStyle.stroke].
///
/// Defaults to [StrokeCap.butt], i.e. no caps.
final StrokeCap strokeCap;
/// The kind of finish to place on the joins between segments.
///
/// This applies to paths drawn when [style] is set to [PaintingStyle.stroke],
/// It does not apply to points drawn as lines with [Canvas.drawPoints].
///
/// Defaults to [StrokeJoin.miter], i.e. sharp corners.
///
/// Some examples of joins:
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4}
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/round_join.mp4}
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/bevel_join.mp4}
///
/// The centers of the line segments are colored in the diagrams above to
/// highlight the joins, but in normal usage the join is the same color as the
/// line.
///
/// See also:
///
/// * [strokeMiterLimit] to control when miters are replaced by bevels when
/// this is set to [StrokeJoin.miter].
/// * [strokeCap] to control what is drawn at the ends of the stroke.
/// * [StrokeJoin] for the definitive list of stroke joins.
final StrokeJoin strokeJoin;
// Must be kept in sync with the default in paint.cc.
static const double _kStrokeMiterLimitDefault = 4.0;
/// The limit for miters to be drawn on segments when the join is set to
/// [StrokeJoin.miter] and the [style] is set to [PaintingStyle.stroke]. If
/// this limit is exceeded, then a [StrokeJoin.bevel] join will be drawn
/// instead. This may cause some 'popping' of the corners of a path if the
/// angle between line segments is animated, as seen in the diagrams below.
///
/// This limit is expressed as a limit on the length of the miter.
///
/// Defaults to 4.0. Using zero as a limit will cause a [StrokeJoin.bevel]
/// join to be used all the time.
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_0_join.mp4}
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4}
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_6_join.mp4}
///
/// The centers of the line segments are colored in the diagrams above to
/// highlight the joins, but in normal usage the join is the same color as the
/// line.
///
/// See also:
///
/// * [strokeJoin] to control the kind of finish to place on the joins
/// between segments.
/// * [strokeCap] to control what is drawn at the ends of the stroke.
final double strokeMiterLimit;
/// A mask filter (for example, a blur) to apply to a shape after it has been
/// drawn but before it has been composited into the image.
///
/// See [MaskFilter] for details.
final MaskFilter maskFilter;
/// Controls the performance vs quality trade-off to use when applying
/// filters, such as [maskFilter], or when drawing images, as with
/// [Canvas.drawImageRect] or [Canvas.drawImageNine].
///
/// Defaults to [FilterQuality.none].
// TODO(ianh): verify that the image drawing methods actually respect this
final FilterQuality filterQuality;
/// The shader to use when stroking or filling a shape.
///
/// When this is null, the [color] is used instead.
///
/// See also:
///
/// * [Gradient], a shader that paints a color gradient.
/// * [ImageShader], a shader that tiles an [Image].
/// * [colorFilter], which overrides [shader].
/// * [color], which is used if [shader] and [colorFilter] are null.
final Shader shader;
/// A color filter to apply when a shape is drawn or when a layer is
/// composited.
///
/// See [ColorFilter] for details.
///
/// When a shape is being drawn, [colorFilter] overrides [color] and [shader].
final ColorFilter colorFilter;
/// Whether the colors of the image are inverted when drawn.
///
/// Inverting the colors of an image applies a new color filter that will
/// be composed with any user provided color filters. This is primarily
/// used for implementing smart invert on iOS.
final bool invertColors;
const PaintStyle({
this.isAntiAlias = true,
this.color = const Color(_kColorDefault),
this.blendMode = BlendMode.srcOver,
this.style = PaintingStyle.fill,
this.strokeWidth = 0.0,
this.strokeCap = StrokeCap.butt,
this.strokeJoin = StrokeJoin.miter,
this.strokeMiterLimit = 4.0,
this.maskFilter, // null
this.filterQuality = FilterQuality.none,
this.shader, // null
this.colorFilter, // null
this.invertColors = false,
});
@override
String toString() {
final StringBuffer result = StringBuffer();
String semicolon = '';
result.write('PaintStyle(');
if (style == PaintingStyle.stroke) {
result.write('$style');
if (strokeWidth != 0.0)
result.write(' ${strokeWidth.toStringAsFixed(1)}');
else
result.write(' hairline');
if (strokeCap != StrokeCap.butt) result.write(' $strokeCap');
if (strokeJoin == StrokeJoin.miter) {
if (strokeMiterLimit != _kStrokeMiterLimitDefault)
result.write(
' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}');
} else {
result.write(' $strokeJoin');
}
semicolon = '; ';
}
if (isAntiAlias != true) {
result.write('${semicolon}antialias off');
semicolon = '; ';
}
if (color != const Color(_kColorDefault)) {
if (color != null)
result.write('$semicolon$color');
else
result.write('${semicolon}no color');
semicolon = '; ';
}
if (blendMode.index != _kBlendModeDefault) {
result.write('$semicolon$blendMode');
semicolon = '; ';
}
if (colorFilter != null) {
result.write('${semicolon}colorFilter: $colorFilter');
semicolon = '; ';
}
if (maskFilter != null) {
result.write('${semicolon}maskFilter: $maskFilter');
semicolon = '; ';
}
if (filterQuality != FilterQuality.none) {
result.write('${semicolon}filterQuality: $filterQuality');
semicolon = '; ';
}
if (shader != null) {
result.write('${semicolon}shader: $shader');
semicolon = '; ';
}
if (invertColors) result.write('${semicolon}invert: $invertColors');
result.write(')');
return result.toString();
}
Paint toPaint() {
Paint paint = Paint();
if (this.isAntiAlias != true) paint.isAntiAlias = this.isAntiAlias;
if (this.color != const Color(_kColorDefault)) paint.color = this.color;
if (this.blendMode != BlendMode.srcOver) paint.blendMode = this.blendMode;
if (this.style != PaintingStyle.fill) paint.style = this.style;
if (this.strokeWidth != 0.0) paint.strokeWidth = this.strokeWidth;
if (this.strokeCap != StrokeCap.butt) paint.strokeCap = this.strokeCap;
if (this.strokeJoin != StrokeJoin.miter) paint.strokeJoin = this.strokeJoin;
if (this.strokeMiterLimit != 4.0)
paint.strokeMiterLimit = this.strokeMiterLimit;
if (this.maskFilter != null) paint.maskFilter = this.maskFilter;
if (this.filterQuality != FilterQuality.none)
paint.filterQuality = this.filterQuality;
if (this.shader != null) paint.shader = this.shader;
if (this.colorFilter != null) paint.colorFilter = this.colorFilter;
if (this.invertColors != false) paint.invertColors = this.invertColors;
return paint;
}
}

View File

@@ -1,6 +1,16 @@
//flutter_bluetooth_serial_example
//https://github.com/edufolly/flutter_bluetooth_serial/tree/master/example
/////////////////////////////////////////////////////////////////
/*
ESP32 | BLUETOOTH CLASSIC | FLUTTER - Let's build BT Serial based on the examples. (Ft. Chat App)
Video Tutorial: https://youtu.be/WUw-_X66dLE
Created by Eric N. (ThatProject)
*/
// Updated 06-21-2021
// Due to Flutter's update, many parts have changed from the tutorial video.
// You need to keep @dart=2.9 before starting main to avoid null safety in Flutter 2
/////////////////////////////////////////////////////////////////
// @dart=2.9
import 'package:flutter/material.dart';
import './MainPage.dart';
@@ -10,6 +20,6 @@ void main() => runApp(new ExampleApplication());
class ExampleApplication extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: MainPage());
return MaterialApp(debugShowCheckedModeBanner: false, home: MainPage());
}
}

View File

@@ -7,35 +7,42 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
version: "2.6.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
characters:
dependency: transitive
description:
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.1.3"
version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.12"
version: "1.15.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -49,7 +56,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
@@ -73,28 +80,21 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.6"
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
scoped_model:
dependency: "direct main"
description:
name: scoped_model
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
@@ -106,55 +106,55 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.15"
version: "0.3.0"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
version: "2.1.0"
sdks:
dart: ">=2.7.0 <3.0.0"
dart: ">=2.12.0 <3.0.0"

View File

@@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
@@ -29,7 +29,6 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3
flutter_bluetooth_serial: ^0.2.2
scoped_model: ^1.0.1
dev_dependencies:
flutter_test: