diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..38ffafed6d350c3250f04fbe7d81347ecb0bb5de --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# flutter_blue_app + +A Flutter application that uses the `flutter_bluetooth_serial` package. + +## Table of Contents +- [Overview](#overview) +- [Setting Up Flutter](#setting-up-flutter) + - [Installing Flutter on macOS](#installing-flutter-on-macos) + - [Installing Flutter on Windows](#installing-flutter-on-windows) +- [Running the Application](#running-the-application) +- [Building the APK](#building-the-apk) +- [Setting Up Android SDK (macOS)](#setting-up-android-sdk-macos) +- [Setting Up Android SDK (Windows)](#setting-up-android-sdk-windows) +- [Getting Started](#getting-started) +- [Additional Resources](#additional-resources) + +## Overview + +This project is a starting point for a Flutter application that integrates Bluetooth functionality using the `flutter_bluetooth_serial` package. + +## Setting Up Flutter + +### Installing Flutter on macOS + +1. **Download Flutter** + ```bash + curl -O https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.10.6-stable.zip + ``` + Extract the downloaded zip and move it to the desired location: + ```bash + unzip flutter_macos_3.10.6-stable.zip + mv flutter ~/development/flutter + ``` + +2. **Update Environment Variables** + Add Flutter to the PATH: + ```bash + export PATH="$PATH:$HOME/development/flutter/bin" + ``` + To make this change permanent, add the line above to `~/.zshrc` or `~/.bashrc`, depending on your shell. + +3. **Install Dependencies** + ```bash + brew install cocoapods + flutter doctor + ``` + Follow any additional installation steps suggested by `flutter doctor`. + +### Installing Flutter on Windows + +1. **Download Flutter** + - Download the Flutter SDK from [Flutter Official Site](https://flutter.dev/docs/get-started/install/windows) + - Extract it to `C:\src\flutter` + +2. **Update Environment Variables** + - Open **Edit environment variables** from the Start menu. + - Add `C:\src\flutter\bin` to the **Path** variable. + +3. **Install Dependencies** + - Run: + ```cmd + flutter doctor + ``` + - Install any missing dependencies reported by `flutter doctor`. + +## Running the Application + +1. **Add the Bluetooth Package Dependency** + In your project directory, run: + ```bash + flutter pub add flutter_bluetooth_serial:^0.4.0 + ``` + +2. **Get Dependencies** + Fetch the package: + ```bash + flutter pub get + ``` + +3. **Create Necessary Files** + If needed, generate missing files: + ```bash + flutter create . + ``` + +4. **Run the App** + Launch the application: + ```bash + flutter run + ``` + +## Building the APK + +1. **Set Up the Android SDK** + Follow the setup instructions below for your operating system. + +2. **Clean and Build** + Once the SDK is configured, run: + ```bash + flutter clean + flutter build apk --release + ``` + +## Setting Up Android SDK (macOS) + +1. **Install Android Command-Line Tools** + ```bash + brew install android-commandlinetools + ``` + +2. **Create the Android SDK Directory** + ```bash + mkdir -p ~/Library/Android/sdk + ``` + +3. **Install Essential SDK Packages** + ```bash + sdkmanager --sdk_root=$HOME/Library/Android/sdk "platform-tools" "platforms;android-34" "build-tools;34.0.0" "cmdline-tools;latest" + ``` + +4. **Configure Environment Variables** + Add these to `~/.zshrc` or `~/.bashrc`: + ```bash + export ANDROID_HOME=$HOME/Library/Android/sdk + export PATH=$ANDROID_HOME/platform-tools:$PATH + ``` + +5. **Verify Installation** + ```bash + flutter doctor + ``` + +## Setting Up Android SDK (Windows) + +1. **Install Android Command-Line Tools** + - Download from [Android Developer](https://developer.android.com/studio#command-tools) + - Extract it to `C:\Android\cmdline-tools\latest` + +2. **Install SDK Packages** + ```cmd + cd C:\Android\cmdline-tools\latest\bin + sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" "cmdline-tools;latest" + ``` + +3. **Configure Environment Variables** + - Add `C:\Android\platform-tools` and `C:\Android\cmdline-tools\latest\bin` to **Path**. + +4. **Verify Installation** + ```cmd + flutter doctor + ``` + +## Getting Started + +This project is a great starting point for your Flutter application. If you're new to Flutter, check out these resources: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +## Additional Resources + +For more help with Flutter development, visit the [Flutter documentation](https://docs.flutter.dev/), which provides tutorials, samples, and a full API reference. + diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..2006f51895ea740a67afe60f04f5cb173a5d77bd --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.flutter_blue_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.flutter_blue_app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..399f6981d5d35475eb18e6068ae67cdd7c731978 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- The INTERNET permission is required for development. Specifically, + the Flutter tool needs it to communicate with the running application + to allow setting breakpoints, to provide hot reload, etc. + --> + <uses-permission android:name="android.permission.INTERNET"/> +</manifest> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..11a51abbc24bd61228f736501460afc505d9ee11 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <application + android:label="flutter_blue_app" + android:name="${applicationName}" + android:icon="@mipmap/ic_launcher"> + <activity + android:name=".MainActivity" + android:exported="true" + android:launchMode="singleTop" + android:taskAffinity="" + android:theme="@style/LaunchTheme" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" + android:hardwareAccelerated="true" + android:windowSoftInputMode="adjustResize"> + <!-- Specifies an Android theme to apply to this Activity as soon as + the Android process has started. This theme is visible to the user + while the Flutter UI initializes. After that, this theme continues + to determine the Window background behind the Flutter UI. --> + <meta-data + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <!-- Don't delete the meta-data below. + This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> + <meta-data + android:name="flutterEmbedding" + android:value="2" /> + </application> + <!-- Required to query activities that can process text, see: + https://developer.android.com/training/package-visibility and + https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. + + In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> + <queries> + <intent> + <action android:name="android.intent.action.PROCESS_TEXT"/> + <data android:mimeType="text/plain"/> + </intent> + </queries> +</manifest> diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000000000000000000000000000000000000..309d3581b1168653d4f085a494c59b31d1e0ce1c --- /dev/null +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,24 @@ +package io.flutter.plugins; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import io.flutter.Log; + +import io.flutter.embedding.engine.FlutterEngine; + +/** + * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Android platform. + */ +@Keep +public final class GeneratedPluginRegistrant { + private static final String TAG = "GeneratedPluginRegistrant"; + public static void registerWith(@NonNull FlutterEngine flutterEngine) { + try { + flutterEngine.getPlugins().add(new io.github.edufolly.flutterbluetoothserial.FlutterBluetoothSerialPlugin()); + } catch (Exception e) { + Log.e(TAG, "Error registering plugin flutter_bluetooth_serial, io.github.edufolly.flutterbluetoothserial.FlutterBluetoothSerialPlugin", e); + } + } +} diff --git a/android/app/src/main/kotlin/com/example/flutter_blue_app/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_blue_app/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..ca8ebef0b47683f4e80b958b99df3f862b0aa0da --- /dev/null +++ b/android/app/src/main/kotlin/com/example/flutter_blue_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.flutter_blue_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..f74085f3f6a2b995f8ad1f9ff7b2c46dc118a9e0 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Modify this file to customize your launch splash screen --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="?android:colorBackground" /> + + <!-- You can insert your own image assets here --> + <!-- <item> + <bitmap + android:gravity="center" + android:src="@mipmap/launch_image" /> + </item> --> +</layer-list> diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..304732f8842013497e14bd02f67a55f2614fb8f7 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Modify this file to customize your launch splash screen --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@android:color/white" /> + + <!-- You can insert your own image assets here --> + <!-- <item> + <bitmap + android:gravity="center" + android:src="@mipmap/launch_image" /> + </item> --> +</layer-list> diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..09d4391482be68e9e4a07fab769b5de337d16eb1 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..06952be745f9fa6fa75196e830d9578eb2ee631d --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> + <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> + <!-- Show a splash screen on the activity. Automatically removed when + the Flutter engine draws its first frame --> + <item name="android:windowBackground">@drawable/launch_background</item> + </style> + <!-- Theme applied to the Android Window as soon as the process has started. + This theme determines the color of the Android Window while your + Flutter UI initializes, as well as behind your Flutter UI while its + running. + + This Theme is only used starting with V2 of Flutter's Android embedding. --> + <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> + <item name="android:windowBackground">?android:colorBackground</item> + </style> +</resources> diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..cb1ef88056edd1caf99a935e434e7ff6943a0ef6 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> + <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> + <!-- Show a splash screen on the activity. Automatically removed when + the Flutter engine draws its first frame --> + <item name="android:windowBackground">@drawable/launch_background</item> + </style> + <!-- Theme applied to the Android Window as soon as the process has started. + This theme determines the color of the Android Window while your + Flutter UI initializes, as well as behind your Flutter UI while its + running. + + This Theme is only used starting with V2 of Flutter's Android embedding. --> + <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> + <item name="android:windowBackground">?android:colorBackground</item> + </style> +</resources> diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..399f6981d5d35475eb18e6068ae67cdd7c731978 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- The INTERNET permission is required for development. Specifically, + the Flutter tool needs it to communicate with the running application + to allow setting breakpoints, to provide hot reload, etc. + --> + <uses-permission android:name="android.permission.INTERNET"/> +</manifest> diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..89176ef44e8c7931fa32322d576e7f3f54d225c9 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register<Delete>("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..f018a61817f55e78bb92ce8df2dda423ec570a4a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..afa1e8eb0a835ba6be2286d1b399c4e47b7a79a0 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100644 index 0000000000000000000000000000000000000000..9d82f78915133e1c35a6ea51252590fb38efac2f --- /dev/null +++ b/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..aec99730b4e8fcd90b57a0e8e01544fea7c31a89 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/local.properties b/android/local.properties new file mode 100644 index 0000000000000000000000000000000000000000..47b0fcdd2a64d33392f174cbb85cf90dd31f4cf2 --- /dev/null +++ b/android/local.properties @@ -0,0 +1,5 @@ +sdk.dir=/Users/pallavi.badanahatti/Library/Android/sdk +flutter.sdk=/Users/pallavi.badanahatti/flutter +flutter.buildMode=debug +flutter.versionName=1.0.0 +flutter.versionCode=1 \ No newline at end of file diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..a439442c20990307318ed9ca06dcc7b0bc49f871 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/lib/AppPage.dart b/lib/AppPage.dart new file mode 100644 index 0000000000000000000000000000000000000000..3e99244d14dc73814d35d0ab7468108d7501b815 --- /dev/null +++ b/lib/AppPage.dart @@ -0,0 +1,233 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; + +class AppPage extends StatefulWidget { + final BluetoothDevice server; + + const AppPage({required this.server}); + + @override + _AppPage createState() => new _AppPage(); +} + + +class _AppPage extends State<AppPage> { + BluetoothConnection? connection; + + String _messageBuffer = ''; + String message = ""; + // new message variables + String sentMessage = ''; + String receivedMessage = ''; + + bool isConnecting = true; + bool get isConnected => connection != null && connection!.isConnected; + + bool isDisconnecting = false; + + @override + void initState() { + super.initState(); + + BluetoothConnection.toAddress(widget.server.address).then((_connection) { + print('Connected to the device'); + connection = _connection; + setState(() { + isConnecting = false; + isDisconnecting = false; + }); + + connection!.input?.listen(_onDataReceived).onDone(() { + // Example: Detect which side closed the connection + // There should be `isDisconnecting` flag to show are we are (locally) + // in middle of disconnecting process, should be set before calling + // `dispose`, `finish` or `close`, which all causes to disconnect. + // If we except the disconnection, `onDone` should be fired as result. + // If we didn't except this (no flag set), it means closing by remote. + if (isDisconnecting) { + print('Disconnecting locally!'); + } else { + print('Disconnected remotely!'); + } + if (this.mounted) { + setState(() {}); + } + }); + }).catchError((error) { + print('Cannot connect, exception occured'); + print(error); + }); + } + + @override + void dispose() { + // Avoid memory leak (`setState` after dispose) and disconnect + if (isConnected) { + isDisconnecting = true; + connection!.dispose(); + connection = null; + } + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Rapsberry Pi Bluetooth Chat"), + ), + body: SafeArea( + child: Column( + children: <Widget>[ + // Text Box to type a message and send + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + onChanged: (String value) { + message = value; + }, + decoration: InputDecoration( + labelText: 'Send a message', + suffixIcon: IconButton( + icon: Icon(Icons.send), + onPressed: isConnected ? () => _sendMessage(message) : null, + ), + ), + ), + ), + SizedBox(height: 20), + // Sent message + Row( + children: <Widget>[ + Expanded( + child: Padding( + // align right with left padding + padding: EdgeInsets.only(left: 70.0), + child: Container( + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(10.0), + ), + // display sent message + child: Text( + '$sentMessage', + style: TextStyle(fontSize: 16, color: Colors.white), + ), + ), + ), + ), + ], + ), + SizedBox(height: 20), + Row( + children: <Widget>[ + Expanded( + child: Padding( + // align left with right padding + padding: EdgeInsets.only(right: 70.0), + child: Container( + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(10.0), + ), + // display received message + child: Text( + '$receivedMessage', + style: TextStyle(fontSize: 16, color: Colors.white), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + + + // void _onDataReceived(Uint8List data) { + // print('Raw data: $data'); + + // // Allocate buffer for parsed data + // int backspacesCounter = 0; + // data.forEach((byte) { + // if (byte == 8 || byte == 127) { + // backspacesCounter++; + // } + // }); + // Uint8List buffer = Uint8List(data.length - backspacesCounter); + // int bufferIndex = buffer.length; + + // // Apply backspace control character + // backspacesCounter = 0; + // for (int i = data.length - 1; i >= 0; i--) { + // if (data[i] == 8 || data[i] == 127) { + // backspacesCounter++; + // } else { + // if (backspacesCounter > 0) { + // backspacesCounter--; + // } else { + // buffer[--bufferIndex] = data[i]; + // } + // } + // } + + // // Create message if there is new line character + // String dataString = String.fromCharCodes(buffer); + // print('Decoded string: $dataString'); + // int index = buffer.indexOf(13); + // if (~index != 0) { + // setState(() { + // receivedMessage = dataString; + // message = backspacesCounter > 0 + // ? _messageBuffer.substring( + // 0, _messageBuffer.length - backspacesCounter) + // : _messageBuffer + dataString.substring(0, index); + + // _messageBuffer = dataString.substring(index); + // }); + // } else { + // _messageBuffer = (backspacesCounter > 0 + // ? _messageBuffer.substring( + // 0, _messageBuffer.length - backspacesCounter) + // : _messageBuffer + dataString); + // } + // } + + void _onDataReceived(Uint8List data) { + print('Raw data: $data'); + + // byte array to string + String dataString = String.fromCharCodes(data); + print('Decoded string: $dataString'); + + setState(() { + receivedMessage = dataString; + }); + } + + + void _sendMessage(String text) async { + text = text.trim(); + + if (text.length > 0) { + try { + connection!.output.add(Uint8List.fromList(utf8.encode(text))); + await connection!.output.allSent; + setState(() { + sentMessage = text; + }); + } catch (e) { + // Ignore error, but notify state + setState(() {}); + } + } + } +} diff --git a/lib/ChatPage.dart b/lib/ChatPage.dart new file mode 100644 index 0000000000000000000000000000000000000000..180bfcde76751fc413b4f2f900d5db2ade2fbb38 --- /dev/null +++ b/lib/ChatPage.dart @@ -0,0 +1,241 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; + +class ChatPage extends StatefulWidget { + final BluetoothDevice server; + + const ChatPage({required this.server}); + + @override + _ChatPage createState() => new _ChatPage(); +} + +class _Message { + int whom; + String text; + + _Message(this.whom, this.text); +} + +class _ChatPage extends State<ChatPage> { + static final clientID = 0; + BluetoothConnection? connection; + + + // ignore: deprecated_member_use + // List<_Message> messages = List<_Message>(); + List<_Message> messages = []; + String _messageBuffer = ''; + + final TextEditingController textEditingController = + new TextEditingController(); + final ScrollController listScrollController = new ScrollController(); + + bool isConnecting = true; + bool get isConnected => connection != null && connection!.isConnected; + + bool isDisconnecting = false; + + @override + void initState() { + super.initState(); + + BluetoothConnection.toAddress(widget.server.address).then((_connection) { + print('Connected to the device'); + connection = _connection; + setState(() { + isConnecting = false; + isDisconnecting = false; + }); + + connection!.input?.listen(_onDataReceived)?.onDone(() { + // Example: Detect which side closed the connection + // There should be `isDisconnecting` flag to show are we are (locally) + // in middle of disconnecting process, should be set before calling + // `dispose`, `finish` or `close`, which all causes to disconnect. + // If we except the disconnection, `onDone` should be fired as result. + // If we didn't except this (no flag set), it means closing by remote. + if (isDisconnecting) { + print('Disconnecting locally!'); + } else { + print('Disconnected remotely!'); + } + if (this.mounted) { + setState(() {}); + } + }); + }).catchError((error) { + print('Cannot connect, exception occured'); + print(error); + }); + } + + @override + void dispose() { + // Avoid memory leak (`setState` after dispose) and disconnect + if (isConnected) { + isDisconnecting = true; + connection!.dispose(); + connection = null; + } + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final List<Row> list = messages.map((_message) { + return Row( + children: <Widget>[ + Container( + child: Text( + (text) { + return text == '/shrug' ? '¯\\_(ツ)_/¯' : text; + }(_message.text.trim()), + style: TextStyle(color: Colors.white)), + padding: EdgeInsets.all(12.0), + margin: EdgeInsets.only(bottom: 8.0, left: 8.0, right: 8.0), + width: 222.0, + decoration: BoxDecoration( + color: + _message.whom == clientID ? Colors.blueAccent : Colors.grey, + borderRadius: BorderRadius.circular(7.0)), + ), + ], + mainAxisAlignment: _message.whom == clientID + ? MainAxisAlignment.end + : MainAxisAlignment.start, + ); + }).toList(); + + return Scaffold( + appBar: AppBar( + title: (isConnecting + ? Text('Connecting chat to ' + (widget.server.name ?? "Unknown") + '...') + : isConnected + ? Text('Live chat with ' + (widget.server.name ?? "Unknown")) + : Text('Chat log with ' + (widget.server.name ?? "Unknown")))), + body: SafeArea( + child: Column( + children: <Widget>[ + Flexible( + child: ListView( + padding: const EdgeInsets.all(12.0), + controller: listScrollController, + children: list), + ), + Row( + children: <Widget>[ + Flexible( + child: Container( + margin: const EdgeInsets.only(left: 16.0), + child: TextField( + style: const TextStyle(fontSize: 15.0), + controller: textEditingController, + decoration: InputDecoration.collapsed( + hintText: isConnecting + ? 'Wait until connected...' + : isConnected + ? 'Type your message...' + : 'Chat got disconnected', + hintStyle: const TextStyle(color: Colors.grey), + ), + enabled: isConnected, + ), + ), + ), + Container( + margin: const EdgeInsets.all(8.0), + child: IconButton( + icon: const Icon(Icons.send), + onPressed: isConnected + ? () => _sendMessage(textEditingController.text) + : null), + ), + ], + ) + ], + ), + ), + ); + } + + void _onDataReceived(Uint8List data) { + // Allocate buffer for parsed data + int backspacesCounter = 0; + data.forEach((byte) { + if (byte == 8 || byte == 127) { + backspacesCounter++; + } + }); + Uint8List buffer = Uint8List(data.length - backspacesCounter); + int bufferIndex = buffer.length; + + // Apply backspace control character + backspacesCounter = 0; + for (int i = data.length - 1; i >= 0; i--) { + if (data[i] == 8 || data[i] == 127) { + backspacesCounter++; + } else { + if (backspacesCounter > 0) { + backspacesCounter--; + } else { + buffer[--bufferIndex] = data[i]; + } + } + } + + // Create message if there is new line character + String dataString = String.fromCharCodes(buffer); + int index = buffer.indexOf(13); + if (~index != 0) { + setState(() { + messages.add( + _Message( + 1, + backspacesCounter > 0 + ? _messageBuffer.substring( + 0, _messageBuffer.length - backspacesCounter) + : _messageBuffer + dataString.substring(0, index), + ), + ); + _messageBuffer = dataString.substring(index); + }); + } else { + _messageBuffer = (backspacesCounter > 0 + ? _messageBuffer.substring( + 0, _messageBuffer.length - backspacesCounter) + : _messageBuffer + dataString); + } + } + + void _sendMessage(String text) async { + text = text.trim(); + textEditingController.clear(); + + if (text.length > 0) { + try { + connection!.output.add(Uint8List.fromList(utf8.encode(text))); + await connection!.output.allSent; + + setState(() { + messages.add(_Message(clientID, text)); + }); + + Future.delayed(Duration(milliseconds: 333)).then((_) { + listScrollController.animateTo( + listScrollController.position.maxScrollExtent, + duration: Duration(milliseconds: 333), + curve: Curves.easeOut); + }); + + } catch (e) { + // Ignore error, but notify state + setState(() {}); + } + } + } +} diff --git a/lib/MainPage.dart b/lib/MainPage.dart new file mode 100644 index 0000000000000000000000000000000000000000..d8cea3c5455ab6b067e196ee961d37ea6380f683 --- /dev/null +++ b/lib/MainPage.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_blue_app/AppPage.dart'; +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; + +import './ChatPage.dart'; +//import './ChatPage2.dart'; + + +class MainPage extends StatefulWidget { + @override + _MainPage createState() => new _MainPage(); +} + +class _MainPage extends State<MainPage> { + + + bool connected = false; + BluetoothDevice? _device; + + @override + void initState() { + super.initState(); + + FlutterBluetoothSerial.instance + .getBondedDevices() + .then((List<BluetoothDevice> bondedDevices) { + setState(() { + for(BluetoothDevice device in bondedDevices){ + if (device.name == "raspberrypi"){ + _device = device; + } + } + }); + }); + } + + + @override + void dispose() { + FlutterBluetoothSerial.instance.setPairingRequestHandler(null); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Chat Demo'), + ), + body: Container( + child: ListView( + children: <Widget>[ + Divider(), + ListTile( + title: Text("Device: " + (_device?.name ?? "No device selected")), + ), + ListTile( + title: ElevatedButton( + child: const Text('Start Chat'), + onPressed: () async { + if (_device != null) { + print('Connect -> selected ' + _device!.address); + _startApp(context, _device!); + } else { + print('Connect -> no device selected'); + } + }, + ), + ), + ], + ), + ), + ); + } + void _startApp(BuildContext context, BluetoothDevice server) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return AppPage(server: server); + }, + ), + ); + } + + void _startChat(BuildContext context, BluetoothDevice server) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return ChatPage(server: server); + }, + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000000000000000000000000000000000000..7e33cabb31aca7124ee0b16a5c93d4dc01c0778f --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_blue_app/MainPage.dart'; + + +void main() => runApp(new ExampleApplication()); + +class ExampleApplication extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp(home: MainPage()); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000000000000000000000000000000000000..332f117f67f374bf0baebe2f3e4cd3b8ea717d9b --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,220 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bluetooth_serial: + dependency: "direct main" + description: + path: "flutter_bluetooth_serial-0.4.0" + relative: true + source: path + version: "0.4.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d671fbde2216bda07c4ffff49cf73042dfb3b035 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,91 @@ +name: flutter_blue_app +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + flutter_bluetooth_serial: + path: ./flutter_bluetooth_serial-0.4.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package