mirror of
https://github.com/0015/ThatProject.git
synced 2026-01-12 01:07:44 +03:00
[Fixed] ESP32 | BLUETOOTH CLASSIC | FLUTTER - Let's build BT Serial based on the examples. (Ft. Chat App)
This commit is contained in:
@@ -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"}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
### Flutter Generated
|
||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
|
*.lock
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
*.swp
|
*.swp
|
||||||
@@ -15,30 +18,878 @@
|
|||||||
*.iws
|
*.iws
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# Visual Studio Code related
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
.vscode/
|
||||||
# is commented out by default.
|
|
||||||
#.vscode/
|
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
**/ios/Flutter/.last_build_id
|
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.flutter-plugins
|
.flutter-plugins
|
||||||
.flutter-plugins-dependencies
|
|
||||||
.packages
|
.packages
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.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/
|
/build/
|
||||||
|
|
||||||
# Web related
|
# Ignore Gradle GUI config
|
||||||
lib/generated_plugin_registrant.dart
|
gradle-app.setting
|
||||||
|
|
||||||
# Symbolication related
|
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||||
app.*.symbols
|
!gradle-wrapper.jar
|
||||||
|
|
||||||
# Obfuscation related
|
# Cache of project
|
||||||
app.*.map.json
|
.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.
|
# Exceptions to above rules.
|
||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
@@ -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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,17 +3,15 @@ import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
|||||||
|
|
||||||
class BluetoothDeviceListEntry extends ListTile {
|
class BluetoothDeviceListEntry extends ListTile {
|
||||||
BluetoothDeviceListEntry({
|
BluetoothDeviceListEntry({
|
||||||
@required BluetoothDevice device,
|
required BluetoothDevice device,
|
||||||
int rssi,
|
required rssi,
|
||||||
GestureTapCallback onTap,
|
required GestureTapCallback onTap,
|
||||||
GestureLongPressCallback onLongPress,
|
|
||||||
bool enabled = true,
|
bool enabled = true,
|
||||||
}) : super(
|
}) : super(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onLongPress: onLongPress,
|
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
leading:
|
leading: Icon(Icons.devices),
|
||||||
Icon(Icons.devices), // @TODO . !BluetoothClass! class aware icon
|
// @TODO . !BluetoothClass! class aware icon
|
||||||
title: Text(device.name ?? "Unknown device"),
|
title: Text(device.name ?? "Unknown device"),
|
||||||
subtitle: Text(device.address.toString()),
|
subtitle: Text(device.address.toString()),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
||||||
|
|
||||||
class ChatPage extends StatefulWidget {
|
class ChatPage extends StatefulWidget {
|
||||||
final BluetoothDevice server;
|
final BluetoothDevice server;
|
||||||
|
|
||||||
const ChatPage({this.server});
|
const ChatPage({required this.server});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChatPage createState() => new _ChatPage();
|
_ChatPage createState() => new _ChatPage();
|
||||||
@@ -22,9 +23,9 @@ class _Message {
|
|||||||
|
|
||||||
class _ChatPage extends State<ChatPage> {
|
class _ChatPage extends State<ChatPage> {
|
||||||
static final clientID = 0;
|
static final clientID = 0;
|
||||||
BluetoothConnection connection;
|
var connection; //BluetoothConnection
|
||||||
|
|
||||||
List<_Message> messages = List<_Message>();
|
List<_Message> messages = [];
|
||||||
String _messageBuffer = '';
|
String _messageBuffer = '';
|
||||||
|
|
||||||
final TextEditingController textEditingController =
|
final TextEditingController textEditingController =
|
||||||
@@ -32,8 +33,6 @@ class _ChatPage extends State<ChatPage> {
|
|||||||
final ScrollController listScrollController = new ScrollController();
|
final ScrollController listScrollController = new ScrollController();
|
||||||
|
|
||||||
bool isConnecting = true;
|
bool isConnecting = true;
|
||||||
bool get isConnected => connection != null && connection.isConnected;
|
|
||||||
|
|
||||||
bool isDisconnecting = false;
|
bool isDisconnecting = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -73,7 +72,7 @@ class _ChatPage extends State<ChatPage> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Avoid memory leak (`setState` after dispose) and disconnect
|
// Avoid memory leak (`setState` after dispose) and disconnect
|
||||||
if (isConnected) {
|
if (isConnected()) {
|
||||||
isDisconnecting = true;
|
isDisconnecting = true;
|
||||||
connection.dispose();
|
connection.dispose();
|
||||||
connection = null;
|
connection = null;
|
||||||
@@ -112,7 +111,7 @@ class _ChatPage extends State<ChatPage> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: (isConnecting
|
title: (isConnecting
|
||||||
? Text('Connecting chat to ' + widget.server.name + '...')
|
? Text('Connecting chat to ' + widget.server.name + '...')
|
||||||
: isConnected
|
: isConnected()
|
||||||
? Text('Live chat with ' + widget.server.name)
|
? Text('Live chat with ' + widget.server.name)
|
||||||
: Text('Chat log with ' + widget.server.name))),
|
: Text('Chat log with ' + widget.server.name))),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
@@ -135,12 +134,12 @@ class _ChatPage extends State<ChatPage> {
|
|||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: isConnecting
|
hintText: isConnecting
|
||||||
? 'Wait until connected...'
|
? 'Wait until connected...'
|
||||||
: isConnected
|
: isConnected()
|
||||||
? 'Type your message...'
|
? 'Type your message...'
|
||||||
: 'Chat got disconnected',
|
: 'Chat got disconnected',
|
||||||
hintStyle: const TextStyle(color: Colors.grey),
|
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),
|
margin: const EdgeInsets.all(8.0),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
onPressed: isConnected
|
onPressed: isConnected()
|
||||||
? () => _sendMessage(textEditingController.text)
|
? () => _sendMessage(textEditingController.text)
|
||||||
: null),
|
: null),
|
||||||
),
|
),
|
||||||
@@ -234,4 +233,8 @@ class _ChatPage extends State<ChatPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConnected() {
|
||||||
|
return connection != null && connection.isConnected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
||||||
|
|
||||||
@@ -15,11 +16,9 @@ class DiscoveryPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DiscoveryPage extends State<DiscoveryPage> {
|
class _DiscoveryPage extends State<DiscoveryPage> {
|
||||||
StreamSubscription<BluetoothDiscoveryResult> _streamSubscription;
|
late StreamSubscription<BluetoothDiscoveryResult> _streamSubscription;
|
||||||
List<BluetoothDiscoveryResult> results = List<BluetoothDiscoveryResult>();
|
List<BluetoothDiscoveryResult> results = [];
|
||||||
bool isDiscovering;
|
late bool isDiscovering;
|
||||||
|
|
||||||
_DiscoveryPage();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -98,53 +97,6 @@ class _DiscoveryPage extends State<DiscoveryPage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop(result.device);
|
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();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.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 './ChatPage.dart';
|
||||||
import './BackgroundCollectingTask.dart';
|
import './DiscoveryPage.dart';
|
||||||
import './BackgroundCollectedPage.dart';
|
|
||||||
|
|
||||||
// import './helpers/LineChart.dart';
|
|
||||||
|
|
||||||
class MainPage extends StatefulWidget {
|
class MainPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@@ -22,13 +17,6 @@ class _MainPage extends State<MainPage> {
|
|||||||
String _address = "...";
|
String _address = "...";
|
||||||
String _name = "...";
|
String _name = "...";
|
||||||
|
|
||||||
Timer _discoverableTimeoutTimer;
|
|
||||||
int _discoverableTimeoutSecondsLeft = 0;
|
|
||||||
|
|
||||||
BackgroundCollectingTask _collectingTask;
|
|
||||||
|
|
||||||
bool _autoAcceptPairingRequests = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -68,10 +56,6 @@ class _MainPage extends State<MainPage> {
|
|||||||
.listen((BluetoothState state) {
|
.listen((BluetoothState state) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_bluetoothState = state;
|
_bluetoothState = state;
|
||||||
|
|
||||||
// Discoverable mode is disabled when Bluetooth gets disabled
|
|
||||||
_discoverableTimeoutTimer = null;
|
|
||||||
_discoverableTimeoutSecondsLeft = 0;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -79,8 +63,6 @@ class _MainPage extends State<MainPage> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
FlutterBluetoothSerial.instance.setPairingRequestHandler(null);
|
FlutterBluetoothSerial.instance.setPairingRequestHandler(null);
|
||||||
_collectingTask?.dispose();
|
|
||||||
_discoverableTimeoutTimer?.cancel();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,91 +114,11 @@ class _MainPage extends State<MainPage> {
|
|||||||
subtitle: Text(_name),
|
subtitle: Text(_name),
|
||||||
onLongPress: null,
|
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(),
|
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(
|
ListTile(
|
||||||
title: RaisedButton(
|
title: TextButton(
|
||||||
child: const Text('Explore discovered devices'),
|
child:
|
||||||
|
const Text('Connect to paired device to chat with ESP32'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final BluetoothDevice selectedDevice =
|
final BluetoothDevice selectedDevice =
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
@@ -229,86 +131,12 @@ class _MainPage extends State<MainPage> {
|
|||||||
|
|
||||||
if (selectedDevice != null) {
|
if (selectedDevice != null) {
|
||||||
print('Discovery -> selected ' + selectedDevice.address);
|
print('Discovery -> selected ' + selectedDevice.address);
|
||||||
|
_startChat(context, selectedDevice);
|
||||||
} else {
|
} else {
|
||||||
print('Discovery -> no device selected');
|
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();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 'package:flutter/material.dart';
|
||||||
|
|
||||||
import './MainPage.dart';
|
import './MainPage.dart';
|
||||||
@@ -10,6 +20,6 @@ void main() => runApp(new ExampleApplication());
|
|||||||
class ExampleApplication extends StatelessWidget {
|
class ExampleApplication extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(home: MainPage());
|
return MaterialApp(debugShowCheckedModeBanner: false, home: MainPage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,35 +7,42 @@ packages:
|
|||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.6.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
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:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.2.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.1.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.12"
|
version: "1.15.0"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -49,7 +56,7 @@ packages:
|
|||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -73,28 +80,21 @@ packages:
|
|||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.6"
|
version: "0.12.10"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.8"
|
version: "1.3.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.0"
|
||||||
scoped_model:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: scoped_model
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -106,55 +106,55 @@ packages:
|
|||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.3"
|
version: "1.10.0"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.1.0"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.1.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.15"
|
version: "0.3.0"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.6"
|
version: "1.3.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.7.0 <3.0.0"
|
dart: ">=2.12.0 <3.0.0"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -29,7 +29,6 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^0.1.3
|
cupertino_icons: ^0.1.3
|
||||||
flutter_bluetooth_serial: ^0.2.2
|
flutter_bluetooth_serial: ^0.2.2
|
||||||
scoped_model: ^1.0.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user