release 1.0
Some checks failed
xiao_pet_tracker / semantic-pull-request (push) Failing after 0s
xiao_pet_tracker / build (push) Failing after 0s
xiao_pet_tracker / spell-check (push) Failing after 0s

This commit is contained in:
baldeau 2024-11-13 21:22:29 +01:00
parent 12f8631c0f
commit 3b4fb7f85c
48 changed files with 1741 additions and 1040 deletions

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2024 Fabian Baldeau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -6,10 +6,6 @@ Absolutely in-development!
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
Generated by the [Very Good CLI][very_good_cli_link] 🤖
A Very Good Project created by Very Good CLI.
---
## Getting Started 🚀

View File

@ -31,7 +31,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace "com.example.verygoodcore.xiao_pet_tracker"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
ndkVersion "27.0.12077973"
compileOptions {
sourceCompatibility JavaVersion.VERSION_17

View File

@ -4,4 +4,24 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- legacy for Android 11 or lower -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
</manifest>

View File

@ -30,9 +30,23 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- legacy for Android 11 or lower -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
</manifest>

View File

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.suppressUnsupportedCompileSdk=34

View File

@ -1,6 +1,7 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

316
android/gradlew vendored Normal file → Executable file
View File

@ -1,74 +1,130 @@
#!/usr/bin/env bash
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Attempt to set APP_HOME
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# 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
;;
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=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"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -77,84 +133,120 @@ 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.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
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
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" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
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"
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

84
android/gradlew.bat vendored
View File

@ -1,3 +1,21 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@ -8,26 +26,30 @@
@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=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@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="-Xmx64m" "-Xms64m"
@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
if %ERRORLEVEL% equ 0 goto execute
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.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -35,54 +57,36 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
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.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
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%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 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
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -19,7 +19,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "com.android.application" version "8.7.1" apply false
id "org.jetbrains.kotlin.android" version "1.9.20" apply false
}

View File

@ -2,28 +2,47 @@ PODS:
- Flutter (1.0.0)
- flutter_blue_plus (0.0.1):
- Flutter
- ObjectBox (4.0.1)
- objectbox_flutter_libs (0.0.1):
- Flutter
- ObjectBox (= 4.0.1)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_blue_plus (from `.symlinks/plugins/flutter_blue_plus/ios`)
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
SPEC REPOS:
trunk:
- ObjectBox
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_blue_plus:
:path: ".symlinks/plugins/flutter_blue_plus/ios"
objectbox_flutter_libs:
:path: ".symlinks/plugins/objectbox_flutter_libs/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_blue_plus: 4837da7d00cf5d441fdd6635b3a57f936778ea96
ObjectBox: 0bc4bb75eea85f6af06b369148b334c2056bbc29
objectbox_flutter_libs: 2ce0da386c780878687c736b528ceaf371573efb
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.13.0
COCOAPODS: 1.16.2

View File

@ -138,7 +138,6 @@
433DDD3DABCB7EF9EEAA70A0 /* Pods-RunnerTests.profile-staging.xcconfig */,
C6A878D9068FE7F973231B67 /* Pods-RunnerTests.profile-development.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@ -223,6 +222,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
7AEE0F6C1047BEE8EA71DFC2 /* [CP] Embed Pods Frameworks */,
5F268BC34EF8F50D378BA2D7 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -332,6 +332,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
5F268BC34EF8F50D378BA2D7 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
67B1C4E35E270A619D5B70BF /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -497,7 +514,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -506,8 +526,9 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -591,7 +612,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -600,8 +624,9 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -680,7 +705,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -689,8 +717,9 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -775,6 +804,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[DEV] Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -783,7 +813,7 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker.dev";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker.dev";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -867,6 +897,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[DEV] Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -875,7 +906,7 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker.dev";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker.dev";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -958,6 +989,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[DEV] Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -966,7 +998,7 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker.dev";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker.dev";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -1052,6 +1084,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg";
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[STG] Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -1060,7 +1093,7 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker.stg";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker.stg";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -1146,6 +1179,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg";
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[STG] Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -1154,7 +1188,7 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker.stg";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker.stg";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -1235,6 +1269,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg";
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = T67F6A44RM;
ENABLE_BITCODE = NO;
FLAVOR_APP_NAME = "[STG] Xiao Pet Tracker";
INFOPLIST_FILE = Runner/Info.plist;
@ -1243,7 +1278,7 @@
"@executable_path/Frameworks",
);
PACKAGE_CONFIG = .dart_tool/package_config.json;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.verygoodcore.xiao-pet-tracker.stg";
PRODUCT_BUNDLE_IDENTIFIER = "sh.avoid.xiao-pet-tracker.stg";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View File

@ -2,11 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@ -17,6 +14,11 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
<key>CFBundleName</key>
<string>$(FLAVOR_APP_NAME)</string>
<key>CFBundlePackageType</key>
@ -29,6 +31,12 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs access to Bluetooth to function properly.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs access to Bluetooth to connect to the xiao microcontroller sensor.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@ -46,13 +54,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs access to Bluetooth to function properly.</string>
</dict>
</plist>

View File

@ -6,9 +6,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:forui/forui.dart';
import 'package:xiao_pet_tracker/app_router/app_router.dart';
import 'package:xiao_pet_tracker/bootstrap.dart';
import 'package:xiao_pet_tracker/home/view/home_page.dart';
import 'package:xiao_pet_tracker/l10n/l10n.dart';
import 'package:xiao_pet_tracker/screens/bluetooth_off_screen.dart';
class AppView extends StatefulWidget {
const AppView({super.key});
@ -42,11 +40,11 @@ class _AppState extends State<AppView> {
@override
Widget build(BuildContext context) {
Widget screen = _adapterState == BluetoothAdapterState.on
? const HomePage()
: BluetoothOffScreen(
adapterState: _adapterState,
);
// Widget screen = _adapterState == BluetoothAdapterState.on
// ? const HomePage()
// : BluetoothOffScreen(
// adapterState: _adapterState,
// );
return MaterialApp.router(
title: 'Xiao Pet Tracker',

View File

@ -14,17 +14,18 @@ class AppRouter extends RootStackRouter {
page: MainRoute.page,
path: '/',
children: [
AutoRoute(
page: HomeRoute.page,
),
AutoRoute(
page: XiaoConnectorRoute.page,
),
AutoRoute(
page: SettingsRoute.page,
page: RecordingsRoute.page,
children: [],
),
],
)
),
AutoRoute(
page: RecordingsDetailsRoute.page,
),
];
}
@ -65,7 +66,7 @@ class MainPage extends StatelessWidget {
animationDuration: const Duration(milliseconds: 650),
routes: const [
XiaoConnectorRoute(),
SettingsRoute(),
RecordingsRoute(),
],
bottomNavigationBuilder: (context, tabsRouter) {
return FBottomNavigationBar(
@ -73,12 +74,12 @@ class MainPage extends StatelessWidget {
onChange: tabsRouter.setActiveIndex,
children: [
FBottomNavigationBarItem(
label: const Text('Home'),
icon: FIcon(FAssets.icons.house),
label: const Text('Xiao Connector'),
icon: FIcon(FAssets.icons.microchip),
),
FBottomNavigationBarItem(
label: const Text('Settings'),
icon: FIcon(FAssets.icons.settings),
label: const Text('Recordings'),
icon: FIcon(FAssets.icons.disc3),
),
],
);

View File

@ -8,36 +8,21 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i5;
import 'package:xiao_pet_tracker/app_router/app_router.dart' as _i2;
import 'package:xiao_pet_tracker/home/view/home_page.dart' as _i1;
import 'package:xiao_pet_tracker/settings/view/settings_page.dart' as _i3;
import 'package:auto_route/auto_route.dart' as _i6;
import 'package:flutter/material.dart' as _i7;
import 'package:xiao_pet_tracker/app_router/app_router.dart' as _i1;
import 'package:xiao_pet_tracker/recordings/view/recordings_details.dart'
as _i2;
import 'package:xiao_pet_tracker/recordings/view/recordings_page.dart' as _i3;
import 'package:xiao_pet_tracker/xiao_connector/view/xiao_connector_page.dart'
as _i4;
import 'package:xiao_pet_tracker/xiao_connector/view/xiao_data_page.dart'
as _i5;
/// generated route for
/// [_i1.HomePage]
class HomeRoute extends _i5.PageRouteInfo<void> {
const HomeRoute({List<_i5.PageRouteInfo>? children})
: super(
HomeRoute.name,
initialChildren: children,
);
static const String name = 'HomeRoute';
static _i5.PageInfo page = _i5.PageInfo(
name,
builder: (data) {
return const _i1.HomePage();
},
);
}
/// generated route for
/// [_i2.MainPage]
class MainRoute extends _i5.PageRouteInfo<void> {
const MainRoute({List<_i5.PageRouteInfo>? children})
/// [_i1.MainPage]
class MainRoute extends _i6.PageRouteInfo<void> {
const MainRoute({List<_i6.PageRouteInfo>? children})
: super(
MainRoute.name,
initialChildren: children,
@ -45,37 +30,90 @@ class MainRoute extends _i5.PageRouteInfo<void> {
static const String name = 'MainRoute';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i2.MainPage();
return const _i1.MainPage();
},
);
}
/// generated route for
/// [_i3.SettingsPage]
class SettingsRoute extends _i5.PageRouteInfo<void> {
const SettingsRoute({List<_i5.PageRouteInfo>? children})
: super(
SettingsRoute.name,
/// [_i2.RecordingsDetailsPage]
class RecordingsDetailsRoute
extends _i6.PageRouteInfo<RecordingsDetailsRouteArgs> {
RecordingsDetailsRoute({
required String uuid,
required String type,
_i7.Key? key,
List<_i6.PageRouteInfo>? children,
}) : super(
RecordingsDetailsRoute.name,
args: RecordingsDetailsRouteArgs(
uuid: uuid,
type: type,
key: key,
),
initialChildren: children,
);
static const String name = 'SettingsRoute';
static const String name = 'RecordingsDetailsRoute';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i3.SettingsPage();
final args = data.argsAs<RecordingsDetailsRouteArgs>();
return _i2.RecordingsDetailsPage(
uuid: args.uuid,
type: args.type,
key: args.key,
);
},
);
}
class RecordingsDetailsRouteArgs {
const RecordingsDetailsRouteArgs({
required this.uuid,
required this.type,
this.key,
});
final String uuid;
final String type;
final _i7.Key? key;
@override
String toString() {
return 'RecordingsDetailsRouteArgs{uuid: $uuid, type: $type, key: $key}';
}
}
/// generated route for
/// [_i3.RecordingsPage]
class RecordingsRoute extends _i6.PageRouteInfo<void> {
const RecordingsRoute({List<_i6.PageRouteInfo>? children})
: super(
RecordingsRoute.name,
initialChildren: children,
);
static const String name = 'RecordingsRoute';
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i3.RecordingsPage();
},
);
}
/// generated route for
/// [_i4.XiaoConnectorPage]
class XiaoConnectorRoute extends _i5.PageRouteInfo<void> {
const XiaoConnectorRoute({List<_i5.PageRouteInfo>? children})
class XiaoConnectorRoute extends _i6.PageRouteInfo<void> {
const XiaoConnectorRoute({List<_i6.PageRouteInfo>? children})
: super(
XiaoConnectorRoute.name,
initialChildren: children,
@ -83,10 +121,29 @@ class XiaoConnectorRoute extends _i5.PageRouteInfo<void> {
static const String name = 'XiaoConnectorRoute';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i4.XiaoConnectorPage();
},
);
}
/// generated route for
/// [_i5.XiaoDataPage]
class XiaoDataRoute extends _i6.PageRouteInfo<void> {
const XiaoDataRoute({List<_i6.PageRouteInfo>? children})
: super(
XiaoDataRoute.name,
initialChildren: children,
);
static const String name = 'XiaoDataRoute';
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i5.XiaoDataPage();
},
);
}

View File

@ -1,2 +0,0 @@
export 'cubit/counter_cubit.dart';
export 'view/counter_page.dart';

View File

@ -1,8 +0,0 @@
import 'package:bloc/bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}

View File

@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:xiao_pet_tracker/counter/counter.dart';
import 'package:xiao_pet_tracker/l10n/l10n.dart';
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: const CounterView(),
);
}
}
class CounterView extends StatelessWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(title: Text(l10n.counterAppBarTitle)),
body: const Center(child: CounterText()),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().increment(),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().decrement(),
child: const Icon(Icons.remove),
),
],
),
);
}
}
class CounterText extends StatelessWidget {
const CounterText({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final count = context.select((CounterCubit cubit) => cubit.state);
return Text('$count', style: theme.textTheme.displayLarge);
}
}

View File

@ -1,296 +0,0 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
@RoutePage()
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const HomeScreen();
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<BluetoothDevice> _systemDevices = [];
List<ScanResult> _scanResults = [];
bool _isScanning = false;
late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
late StreamSubscription<bool> _isScanningSubscription;
bool _connected = false;
bool _found = false;
BluetoothDevice? device;
List<BluetoothService> _services = [];
Future<void> scanDevices() async {
if (await FlutterBluePlus.isSupported == false) {
print("Bluetooth not supported by this device");
return;
}
final scannedDevices = <ScanResult>{};
const timeout = Duration(seconds: 3);
await FlutterBluePlus.startScan(timeout: timeout);
final sub =
FlutterBluePlus.scanResults.expand((e) => e).listen(scannedDevices.add);
// await Future.delayed(timeout);
sub.cancel();
scannedDevices.forEach(print);
print('Scanned devices: ${scannedDevices.length}');
}
@override
void initState() {
super.initState();
_scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
_scanResults = results;
if (mounted) {
setState(() {});
}
}, onError: (e) {
print('Scan Results Subscription Error: $e');
});
_isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
_isScanning = state;
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_scanResultsSubscription.cancel();
_isScanningSubscription.cancel();
super.dispose();
}
Future onScanPressed() async {
try {
_systemDevices = await FlutterBluePlus.systemDevices([]);
} catch (e) {
print('System Devices Error: $e');
}
try {
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
} catch (e) {
print('Start Scan Error: $e');
}
if (mounted) {
setState(() {});
}
}
Future onConnectPressed() async {
try {
_systemDevices = await FlutterBluePlus.systemDevices([]);
} catch (e) {
print('System Devices Error: $e');
}
try {
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
} catch (e) {
print('Start Scan Error: $e');
}
FlutterBluePlus.scanResults.listen((results) async {
for (ScanResult r in results) {
if (kDebugMode) {
print(r);
}
if (r.device.advName == "Go Bluetooth") {
await FlutterBluePlus.stopScan();
device = r.device;
print("FOUND BLUETOOTH DEVICE");
await device?.connect();
try {
_services = await device?.discoverServices() ?? [];
} catch (e) {
print("Discover Services Error: $e");
}
if (mounted) {
setState(() {
_connected = true;
});
}
}
}
});
}
Future onStopPressed() async {
try {
FlutterBluePlus.stopScan();
} catch (e) {
print('Stop Scan Error: $e');
}
}
Future onRefresh() {
if (_isScanning == false) {
FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
}
if (mounted) {
setState(() {});
}
return Future.delayed(const Duration(milliseconds: 500));
}
Future<void> handleDisconnect() async {
await device?.disconnect();
}
Future onDisconnectPressed() async {
await handleDisconnect();
setState(() {
_connected = false;
});
}
@override
Future<void> deactivate() async {
super.deactivate();
await handleDisconnect();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('XIAO Sense'),
),
body: SingleChildScrollView(
child: Column(
children: [
// ListView.builder(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
// itemCount: _scanResults.length,
// itemBuilder: (context, index) {
// return ListTile(
// title: Text(_scanResults[index].device.remoteId.toString()),
// subtitle: Text(_scanResults[index].device.advName),
// );
// },
// ),
if (_connected) Text('Connected!'),
ElevatedButton(
// enabled: !_isScanning,
onPressed: () async {
// await Future<void>.delayed(const Duration(seconds: 2));
// onScanPressed();
if (_connected) {
onDisconnectPressed();
} else {
onConnectPressed();
}
},
// icon: _isScanning
// ? const SizedBox.square(
// dimension: 16,
// child: CircularProgressIndicator(),
// )
// : null,
child: _isScanning
? const Text('Please wait')
: _connected
? const Text('Disconnect')
: const Text('Connect'),
),
ElevatedButton(
onPressed: () async {
print('Toggle LED');
print(_services);
final ledService = _services
.where(
(s) =>
s.serviceUuid ==
Guid('a0b40001-926d-4d61-98df-8c5c62ee53b3'),
)
.first
.characteristics
.where((c) =>
c.characteristicUuid ==
Guid('a0b40002-926d-4d61-98df-8c5c62ee53b3'))
.first;
ledService.write([
0,
1,
0,
]);
// await Future.delayed(Duration(seconds: 2));
List<int> ledServiceValue = await ledService.read();
print("READ LED SERVICE: $ledServiceValue");
},
child: Text('Toggle LED'),
),
ElevatedButton(
onPressed: () async {
print('Toggle LED');
print(_services);
final ledService = _services
.where(
(s) =>
s.serviceUuid ==
Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
.where(
(c) =>
c.characteristicUuid ==
Guid('61636365-6c65-7261-7469-6f6e44617461'),
)
.first;
final senseSubscription =
ledService.onValueReceived.listen((value) {
print("UPDATE: ${String.fromCharCodes(value)}");
});
device!.cancelWhenDisconnected(senseSubscription);
await ledService.setNotifyValue(true);
// await ledService.write([1, 2, 3]);
// await Future.delayed(Duration(seconds: 2));
// List<int> ledServiceValue = await ledService.read();
// print("READ Sense SERVICE: $ledServiceValue");
// print(
// "READ Sense SERVICE: ${String.fromCharCodes(ledServiceValue)}");
},
child: Text('Read Sense'),
)
],
),
),
);
}
}

View File

@ -1 +0,0 @@
export 'home_page.dart';

View File

@ -4,80 +4,120 @@
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "2:23371849066855038",
"lastPropertyId": "10:4052938287274443905",
"id": "1:8883777039160106085",
"lastPropertyId": "28:5600095333294587898",
"name": "CapturePoint",
"properties": [
{
"id": "1:9209282349575641428",
"id": "1:7031141970833098443",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:8441629944202144182",
"name": "rotationX",
"type": 9
},
{
"id": "3:3805193742978753321",
"name": "rotationY",
"type": 9
},
{
"id": "4:7632102454091387966",
"name": "rotationZ",
"type": 9
},
{
"id": "5:1175247580397683930",
"name": "accelerationX",
"type": 9
},
{
"id": "6:133552602314920473",
"name": "accelerationY",
"type": 9
},
{
"id": "7:7272664476171529007",
"name": "accelerationZ",
"type": 9
},
{
"id": "9:5697556233714735057",
"id": "2:9089711773119462722",
"name": "type",
"type": 9
},
{
"id": "10:4052938287274443905",
"name": "millisecondsSinceEpoch",
"id": "12:8018734828546727200",
"name": "uuid",
"type": 9
},
{
"id": "21:5571567596004635004",
"name": "rotationX",
"type": 6
},
{
"id": "22:128950109858162458",
"name": "rotationY",
"type": 6
},
{
"id": "23:572745053585623035",
"name": "rotationZ",
"type": 6
},
{
"id": "24:174662964807733060",
"name": "accelerationX",
"type": 6
},
{
"id": "25:2607558191101966289",
"name": "accelerationY",
"type": 6
},
{
"id": "26:8078744267935466967",
"name": "accelerationZ",
"type": 6
},
{
"id": "27:2235290443011784871",
"name": "millisecondsSinceEpochReceived",
"type": 6
},
{
"id": "28:5600095333294587898",
"name": "millisecondsSinceEpochSend",
"type": 6
}
],
"relations": []
},
{
"id": "2:1642885530071590702",
"lastPropertyId": "3:4134744801341910087",
"name": "CaptureBox",
"properties": [
{
"id": "1:6772033063452157797",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:6118609604439630810",
"name": "type",
"type": 9
},
{
"id": "3:4134744801341910087",
"name": "uuid",
"type": 9
}
],
"relations": []
}
],
"lastEntityId": "2:23371849066855038",
"lastEntityId": "2:1642885530071590702",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
6206931177901930822
],
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [
696937191447548336,
7375635633551465262,
7972299062087181706,
5044874535120106939,
6473009355337784814,
8948425706967252950,
5134040367670771080,
4879888592430478383,
2890906455219707986
5928562934131159642,
5591187076567204697,
595530508372810754,
376663189826498742,
2249450669900993772,
3338488084769147616,
6118432389289072923,
652072338599943564,
4974899424217069697,
6174239378189558256,
890878501153569237,
1635504337236403097,
3739937748546759727,
1440167199258268261,
90858017595682556,
8864904344725714676,
6079624305294726236
],
"retiredRelationUids": [],
"version": 1

View File

@ -12,7 +12,7 @@ class ObjectBox {
static Future<ObjectBox> create() async {
final docsDir = await getApplicationDocumentsDirectory();
final store =
await openStore(directory: p.join(docsDir.path, 'obx-pet-tracker'));
await openStore(directory: p.join(docsDir.path, 'obx-pet-tracker-2'));
return ObjectBox._create(store);
}
}

View File

@ -14,61 +14,96 @@ import 'package:objectbox/internal.dart'
import 'package:objectbox/objectbox.dart' as obx;
import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
import 'xiao_connector/models/capture_box.dart';
import 'xiao_connector/models/capture_point.dart';
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
final _entities = <obx_int.ModelEntity>[
obx_int.ModelEntity(
id: const obx_int.IdUid(2, 23371849066855038),
id: const obx_int.IdUid(1, 8883777039160106085),
name: 'CapturePoint',
lastPropertyId: const obx_int.IdUid(10, 4052938287274443905),
lastPropertyId: const obx_int.IdUid(28, 5600095333294587898),
flags: 0,
properties: <obx_int.ModelProperty>[
obx_int.ModelProperty(
id: const obx_int.IdUid(1, 9209282349575641428),
id: const obx_int.IdUid(1, 7031141970833098443),
name: 'id',
type: 6,
flags: 1),
obx_int.ModelProperty(
id: const obx_int.IdUid(2, 8441629944202144182),
name: 'rotationX',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(3, 3805193742978753321),
name: 'rotationY',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(4, 7632102454091387966),
name: 'rotationZ',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(5, 1175247580397683930),
name: 'accelerationX',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(6, 133552602314920473),
name: 'accelerationY',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(7, 7272664476171529007),
name: 'accelerationZ',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(9, 5697556233714735057),
id: const obx_int.IdUid(2, 9089711773119462722),
name: 'type',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(10, 4052938287274443905),
name: 'millisecondsSinceEpoch',
id: const obx_int.IdUid(12, 8018734828546727200),
name: 'uuid',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(21, 5571567596004635004),
name: 'rotationX',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(22, 128950109858162458),
name: 'rotationY',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(23, 572745053585623035),
name: 'rotationZ',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(24, 174662964807733060),
name: 'accelerationX',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(25, 2607558191101966289),
name: 'accelerationY',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(26, 8078744267935466967),
name: 'accelerationZ',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(27, 2235290443011784871),
name: 'millisecondsSinceEpochReceived',
type: 6,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(28, 5600095333294587898),
name: 'millisecondsSinceEpochSend',
type: 6,
flags: 0)
],
relations: <obx_int.ModelRelation>[],
backlinks: <obx_int.ModelBacklink>[]),
obx_int.ModelEntity(
id: const obx_int.IdUid(2, 1642885530071590702),
name: 'CaptureBox',
lastPropertyId: const obx_int.IdUid(3, 4134744801341910087),
flags: 0,
properties: <obx_int.ModelProperty>[
obx_int.ModelProperty(
id: const obx_int.IdUid(1, 6772033063452157797),
name: 'id',
type: 6,
flags: 1),
obx_int.ModelProperty(
id: const obx_int.IdUid(2, 6118609604439630810),
name: 'type',
type: 9,
flags: 0),
obx_int.ModelProperty(
id: const obx_int.IdUid(3, 4134744801341910087),
name: 'uuid',
type: 9,
flags: 0)
],
relations: <obx_int.ModelRelation>[],
@ -110,22 +145,30 @@ Future<obx.Store> openStore(
obx_int.ModelDefinition getObjectBoxModel() {
final model = obx_int.ModelInfo(
entities: _entities,
lastEntityId: const obx_int.IdUid(2, 23371849066855038),
lastEntityId: const obx_int.IdUid(2, 1642885530071590702),
lastIndexId: const obx_int.IdUid(0, 0),
lastRelationId: const obx_int.IdUid(0, 0),
lastSequenceId: const obx_int.IdUid(0, 0),
retiredEntityUids: const [6206931177901930822],
retiredEntityUids: const [],
retiredIndexUids: const [],
retiredPropertyUids: const [
696937191447548336,
7375635633551465262,
7972299062087181706,
5044874535120106939,
6473009355337784814,
8948425706967252950,
5134040367670771080,
4879888592430478383,
2890906455219707986
5928562934131159642,
5591187076567204697,
595530508372810754,
376663189826498742,
2249450669900993772,
3338488084769147616,
6118432389289072923,
652072338599943564,
4974899424217069697,
6174239378189558256,
890878501153569237,
1635504337236403097,
3739937748546759727,
1440167199258268261,
90858017595682556,
8864904344725714676,
6079624305294726236
],
retiredRelationUids: const [],
modelVersion: 5,
@ -142,23 +185,20 @@ obx_int.ModelDefinition getObjectBoxModel() {
object.id = id;
},
objectToFB: (CapturePoint object, fb.Builder fbb) {
final rotationXOffset = fbb.writeString(object.rotationX);
final rotationYOffset = fbb.writeString(object.rotationY);
final rotationZOffset = fbb.writeString(object.rotationZ);
final accelerationXOffset = fbb.writeString(object.accelerationX);
final accelerationYOffset = fbb.writeString(object.accelerationY);
final accelerationZOffset = fbb.writeString(object.accelerationZ);
final typeOffset = fbb.writeString(object.type);
fbb.startTable(11);
final uuidOffset = fbb.writeString(object.uuid);
fbb.startTable(29);
fbb.addInt64(0, object.id);
fbb.addOffset(1, rotationXOffset);
fbb.addOffset(2, rotationYOffset);
fbb.addOffset(3, rotationZOffset);
fbb.addOffset(4, accelerationXOffset);
fbb.addOffset(5, accelerationYOffset);
fbb.addOffset(6, accelerationZOffset);
fbb.addOffset(8, typeOffset);
fbb.addInt64(9, object.millisecondsSinceEpoch);
fbb.addOffset(1, typeOffset);
fbb.addOffset(11, uuidOffset);
fbb.addInt64(20, object.rotationX);
fbb.addInt64(21, object.rotationY);
fbb.addInt64(22, object.rotationZ);
fbb.addInt64(23, object.accelerationX);
fbb.addInt64(24, object.accelerationY);
fbb.addInt64(25, object.accelerationZ);
fbb.addInt64(26, object.millisecondsSinceEpochReceived);
fbb.addInt64(27, object.millisecondsSinceEpochSend);
fbb.finish(fbb.endTable());
return object.id;
},
@ -166,33 +206,67 @@ obx_int.ModelDefinition getObjectBoxModel() {
final buffer = fb.BufferContext(fbData);
final rootOffset = buffer.derefObject(0);
final typeParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 20, '');
final rotationXParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 6, '');
final rotationYParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 8, '');
final rotationZParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 10, '');
final uuidParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 26, '');
final rotationXParam =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0);
final rotationYParam =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 46, 0);
final rotationZParam =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 48, 0);
final accelerationXParam =
const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 12, '');
const fb.Int64Reader().vTableGet(buffer, rootOffset, 50, 0);
final accelerationYParam =
const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 14, '');
const fb.Int64Reader().vTableGet(buffer, rootOffset, 52, 0);
final accelerationZParam =
const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 16, '');
final millisecondsSinceEpochParam =
const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 22);
const fb.Int64Reader().vTableGet(buffer, rootOffset, 54, 0);
final millisecondsSinceEpochReceivedParam =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 56, 0);
final millisecondsSinceEpochSendParam =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 58, 0);
final object = CapturePoint(
type: typeParam,
uuid: uuidParam,
rotationX: rotationXParam,
rotationY: rotationYParam,
rotationZ: rotationZParam,
accelerationX: accelerationXParam,
accelerationY: accelerationYParam,
accelerationZ: accelerationZParam,
millisecondsSinceEpoch: millisecondsSinceEpochParam)
millisecondsSinceEpochReceived:
millisecondsSinceEpochReceivedParam,
millisecondsSinceEpochSend: millisecondsSinceEpochSendParam)
..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
return object;
}),
CaptureBox: obx_int.EntityDefinition<CaptureBox>(
model: _entities[1],
toOneRelations: (CaptureBox object) => [],
toManyRelations: (CaptureBox object) => {},
getId: (CaptureBox object) => object.id,
setId: (CaptureBox object, int id) {
object.id = id;
},
objectToFB: (CaptureBox object, fb.Builder fbb) {
final typeOffset = fbb.writeString(object.type);
final uuidOffset = fbb.writeString(object.uuid);
fbb.startTable(4);
fbb.addInt64(0, object.id);
fbb.addOffset(1, typeOffset);
fbb.addOffset(2, uuidOffset);
fbb.finish(fbb.endTable());
return object.id;
},
objectFromFB: (obx.Store store, ByteData fbData) {
final buffer = fb.BufferContext(fbData);
final rootOffset = buffer.derefObject(0);
final typeParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 6, '');
final uuidParam = const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 8, '');
final object = CaptureBox(type: typeParam, uuid: uuidParam)
..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
return object;
@ -208,35 +282,58 @@ class CapturePoint_ {
static final id =
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[0]);
/// See [CapturePoint.type].
static final type =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[1]);
/// See [CapturePoint.uuid].
static final uuid =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[2]);
/// See [CapturePoint.rotationX].
static final rotationX =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[1]);
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[3]);
/// See [CapturePoint.rotationY].
static final rotationY =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[2]);
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[4]);
/// See [CapturePoint.rotationZ].
static final rotationZ =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[3]);
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[5]);
/// See [CapturePoint.accelerationX].
static final accelerationX =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[4]);
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[6]);
/// See [CapturePoint.accelerationY].
static final accelerationY =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[5]);
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[7]);
/// See [CapturePoint.accelerationZ].
static final accelerationZ =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[6]);
/// See [CapturePoint.type].
static final type =
obx.QueryStringProperty<CapturePoint>(_entities[0].properties[7]);
/// See [CapturePoint.millisecondsSinceEpoch].
static final millisecondsSinceEpoch =
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[8]);
/// See [CapturePoint.millisecondsSinceEpochReceived].
static final millisecondsSinceEpochReceived =
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[9]);
/// See [CapturePoint.millisecondsSinceEpochSend].
static final millisecondsSinceEpochSend =
obx.QueryIntegerProperty<CapturePoint>(_entities[0].properties[10]);
}
/// [CaptureBox] entity fields to define ObjectBox queries.
class CaptureBox_ {
/// See [CaptureBox.id].
static final id =
obx.QueryIntegerProperty<CaptureBox>(_entities[1].properties[0]);
/// See [CaptureBox.type].
static final type =
obx.QueryStringProperty<CaptureBox>(_entities[1].properties[1]);
/// See [CaptureBox.uuid].
static final uuid =
obx.QueryStringProperty<CaptureBox>(_entities[1].properties[2]);
}

View File

@ -0,0 +1,208 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:csv/csv.dart';
import 'package:ditredi/ditredi.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:ditredi/ditredi.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
@RoutePage()
class RecordingsDetailsPage extends StatefulWidget {
const RecordingsDetailsPage({
required this.uuid,
required this.type,
super.key,
});
final String uuid;
final String type;
@override
State<RecordingsDetailsPage> createState() => _RecordingsDetailsPageState();
}
class _RecordingsDetailsPageState extends State<RecordingsDetailsPage> {
final _controllerRotation = DiTreDiController();
final _controllerAcceleration = DiTreDiController();
List<CapturePoint> _capturePoints = [];
@override
void initState() {
_capturePoints =
context.read<XiaoConnectorCubit>().getCapturePointsOfUuid(widget.uuid);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details: ${widget.type}'),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(
height: 16,
),
Text(
'Capture Start Time: ${DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend)}',
),
Text(
'Capture End Time: ${DateTime.fromMillisecondsSinceEpoch(_capturePoints.first.millisecondsSinceEpochSend)}',
),
const SizedBox(
height: 8,
),
Text('Amount of captured points: ${_capturePoints.length}'),
const SizedBox(
height: 16,
),
ElevatedButton(
onPressed: () async {
// await Permission.storage.request();
final Directory? downloadsDir = (Platform.isIOS)
? await getApplicationDocumentsDirectory()
: await getDownloadsDirectory();
File f = File(
downloadsDir!.path + "/${widget.type}_${widget.uuid}.csv");
List<List<dynamic>> rows = [];
List<dynamic> row = [];
row.add('sendTimeStamp');
row.add('receivedTimeStamp');
row.add('accelerationX');
row.add('accelerationY');
row.add('accelerationZ');
row.add('rotationX');
row.add('rotationY');
row.add('rotationZ');
rows.add(row);
for (var i = 0; i < _capturePoints.length; i++) {
List<dynamic> row = [];
row.add(_capturePoints[i].millisecondsSinceEpochSend);
row.add(_capturePoints[i].millisecondsSinceEpochReceived);
row.add(_capturePoints[i].accelerationX);
row.add(_capturePoints[i].accelerationY);
row.add(_capturePoints[i].accelerationZ);
row.add(_capturePoints[i].rotationX);
row.add(_capturePoints[i].rotationY);
row.add(_capturePoints[i].rotationZ);
rows.add(row);
}
String csv = const ListToCsvConverter().convert(rows);
f.writeAsString(csv);
},
child: const Text('Export to CSV'),
),
const SizedBox(
height: 16,
),
const Text('Raw Rotation Data'),
Padding(
padding: const EdgeInsets.all(8),
child: Container(
height: 400,
// width: 400,
// color: Color.fromARGB(255, 0, 0, 0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 0, 0, 0),
border:
Border.all(color: const Color.fromARGB(255, 0, 0, 0))),
child: DiTreDiDraggable(
controller: _controllerRotation,
child: DiTreDi(
figures: [
Cube3D(
0,
Vector3(0, 0, 0),
color: const Color.fromARGB(0, 128, 0, 0),
),
..._capturePoints.map(
(p) => Point3D(
Vector3(
p.rotationX.toDouble(),
p.rotationY.toDouble(),
p.rotationZ.toDouble(),
),
width: 2,
color: const Color.fromARGB(255, 255, 255, 255),
),
)
],
controller: _controllerRotation,
),
),
),
),
const Text('Raw Acceleration Data'),
Padding(
padding: const EdgeInsets.all(8),
child: Container(
height: 400,
// width: 400,
// color: Color.fromARGB(255, 0, 0, 0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 0, 0, 0),
border:
Border.all(color: const Color.fromARGB(255, 0, 0, 0))),
child: DiTreDiDraggable(
controller: _controllerAcceleration,
child: DiTreDi(
figures: [
Cube3D(
0,
Vector3(0, 0, 0),
color: const Color.fromARGB(0, 128, 0, 0),
),
..._capturePoints.map(
(p) => Point3D(
Vector3(
p.accelerationX.toDouble(),
p.accelerationY.toDouble(),
p.accelerationZ.toDouble(),
),
width: 2,
color: const Color.fromARGB(255, 255, 255, 255),
),
)
],
controller: _controllerAcceleration,
),
),
),
),
const SizedBox(
height: 100,
),
// ListView.builder(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
// itemCount: _capturePoints.length,
// itemBuilder: (context, i) {
// return ListTile(
// title: Text('data'),
// );
// },
// ),
],
),
),
);
}
}

View File

@ -0,0 +1,70 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:forui/forui.dart';
import 'package:xiao_pet_tracker/app_router/app_router.gr.dart';
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.dart';
import 'package:xiao_pet_tracker/xiao_connector/view/xiao_connector_page.dart';
@RoutePage()
class RecordingsPage extends StatelessWidget {
const RecordingsPage({super.key});
@override
Widget build(BuildContext context) {
return const RecordingsView();
}
}
class RecordingsView extends StatefulWidget {
const RecordingsView({super.key});
@override
State<RecordingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<RecordingsView> {
List<CaptureBox> _captureBoxes = [];
@override
void initState() {
_captureBoxes = context.read<XiaoConnectorCubit>().getAllCaptureBoxes();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Recordings'),
),
body: RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: _captureBoxes.length,
itemBuilder: (context, i) {
return ListTile(
leading: FIcon(FAssets.icons.pawPrint),
title: Text('Collection Type: ${_captureBoxes[i].type}'),
subtitle: Text('Uuid: ${_captureBoxes[i].uuid}'),
onTap: () {
AutoRouter.of(context).push(
RecordingsDetailsRoute(
type: _captureBoxes[i].type,
uuid: _captureBoxes[i].uuid,
),
);
},
);
},
),
),
);
}
Future<void> _pullRefresh() async {
_captureBoxes = context.read<XiaoConnectorCubit>().getAllCaptureBoxes();
setState(() {});
}
}

View File

@ -0,0 +1 @@
export 'recordings_page.dart';

View File

@ -1,68 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({
super.key,
this.adapterState,
});
final BluetoothAdapterState? adapterState;
Widget buildBluetoothOffIcon(BuildContext context) {
return const Icon(
Icons.bluetooth_disabled,
size: 200,
color: Colors.white54,
);
}
Widget buildTitle(BuildContext context) {
String? state = adapterState?.toString().split(".").last;
return Text(
'Bluetooth Adapter is ${state != null ? state : 'not available'}',
style: Theme.of(context)
.primaryTextTheme
.titleSmall
?.copyWith(color: Colors.white),
);
}
Widget buildTurnOnButton(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: ElevatedButton(
onPressed: () async {
try {
if (Platform.isAndroid) {
await FlutterBluePlus.turnOn();
}
} catch (e) {
print("Error Turning On: $e");
}
},
child: const Text('TURN ON'),
),
);
}
@override
Widget build(BuildContext context) {
return ScaffoldMessenger(
child: Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildBluetoothOffIcon(context),
buildTitle(context),
if (Platform.isAndroid) buildTurnOnButton(context),
],
),
),
));
}
}

View File

@ -1 +0,0 @@
export 'view/view.dart';

View File

@ -1,23 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return const SettingsView();
}
}
class SettingsView extends StatelessWidget {
const SettingsView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Settings'),
);
}
}

View File

@ -1 +0,0 @@
export 'settings_page.dart';

View File

@ -1 +0,0 @@
const bleName = "Go Bluetooth";

View File

@ -1,6 +1,2 @@
const uuidLSM6DS3TRService = '4c534d36-4453-3354-5253-657276696365';
const uuidAccelerationData = '61636365-6c65-7261-7469-6f6e44617461';
//tempSenseService = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x53, 0x65, 0x6E, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
//temperatureSense = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x6E, 0x73, 0x65}

View File

@ -4,10 +4,13 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
import 'package:objectbox/objectbox.dart';
import 'package:uuid/uuid.dart';
import 'package:xiao_pet_tracker/bootstrap.dart';
import 'package:xiao_pet_tracker/objectbox.dart';
import 'package:xiao_pet_tracker/objectbox.g.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
import 'package:xiao_pet_tracker/xiao_connector/utils/utils.dart';
part 'xiao_connector_state.dart';
@ -15,36 +18,38 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
XiaoConnectorCubit() : super(const XiaoConnectorState());
late BluetoothDevice device;
List<BluetoothDevice> _systemDevices = [];
List<BluetoothService> _services = [];
late BluetoothCharacteristic _senseService;
late StreamSubscription<List<int>> dataCapturingSubscription;
final ObjectBox _objectBox = getIt<ObjectBox>();
late final Box<CapturePoint> _capturePointsBox;
late final Box<CaptureBox> _captureBoxes;
bool gotRotationX = false;
bool gotRotationY = false;
bool gotRotationZ = false;
bool gotRotation = false;
bool gotAcceleration = false;
bool gotTimeStamp = false;
late String _rotX;
late String _rotY;
late String _rotZ;
late String _accel;
int _rotX = 0;
int _rotY = 0;
int _rotZ = 0;
int _accX = 0;
int _accY = 0;
int _accZ = 0;
int _millisecondsSinceEpochSend = 0;
String _captureType = '';
String _uuid = '';
bool isRecording = false;
Future<void> init() async {
_capturePointsBox = _objectBox.store.box<CapturePoint>();
_captureBoxes = _objectBox.store.box<CaptureBox>();
}
Future<void> connect() async {
emit(state.copyWith(status: XiaoConnectorStatus.loading));
try {
_systemDevices = await FlutterBluePlus.systemDevices([]);
} catch (e) {
emit(state.copyWith(status: XiaoConnectorStatus.failure));
throw Exception('System Devices Error: $e');
}
try {
// scan for devices for 15 seconds
@ -95,8 +100,46 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
);
}
Future<void> startCapturing({required String captureType}) async {
Future<void> setCapturingOn() async {
final senseService = _services
.where(
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
.where(
(c) =>
c.characteristicUuid ==
Guid('63617074-7572-696E-6753-657276696365'),
)
.first;
await senseService.write([1]);
}
Uint8List int64BigEndianBytes(int value) =>
Uint8List(8)..buffer.asByteData().setInt64(0, value);
Future<void> setTime(int millisSinceEpoch) async {
final senseService = _services
.where(
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
)
.first
.characteristics
.where(
(c) =>
c.characteristicUuid ==
Guid('756E6978-5469-6D65-5374-616D70527374'),
)
.first;
final List<int> bytes = int64BigEndianBytes(millisSinceEpoch);
await senseService.write(bytes);
}
Future<void> startCapturing() async {
_senseService = _services
.where(
(s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
)
@ -109,57 +152,70 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
)
.first;
dataCapturingSubscription = senseService.onValueReceived.listen((value) {
debugPrint('UPDATE: $value');
debugPrint('UPDATE: ${String.fromCharCodes(value)}');
final valueAsString = String.fromCharCodes(value);
if (valueAsString.startsWith('rX=')) {
gotRotationX = true;
_rotX = valueAsString.replaceAll('rX=', '');
dataCapturingSubscription = _senseService.onValueReceived.listen((value) {
// rotation
if (value.last == 1) {
_rotX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
_rotY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
_rotZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
gotRotation = true;
}
if (valueAsString.startsWith('rY=')) {
gotRotationY = true;
_rotY = valueAsString.replaceAll('rY=', '');
}
if (valueAsString.startsWith('rZ=')) {
gotRotationZ = true;
_rotZ = valueAsString.replaceAll('rZ=', '');
}
if (valueAsString.contains(',')) {
// acceleration
if (value.last == 2) {
_accX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
_accY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
_accZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
gotAcceleration = true;
_accel = valueAsString;
}
if (gotAcceleration && gotRotationX && gotRotationY && gotRotationZ) {
final accelerations = _accel.split(',');
if (value.last == 3) {
final timeStamp = fromBytesToInt64(
value[0],
value[1],
value[2],
value[3],
value[4],
value[5],
value[6],
value[7],
);
_millisecondsSinceEpochSend = timeStamp;
gotTimeStamp = true;
}
if (gotAcceleration && gotRotation && gotTimeStamp) {
final capturePoint = CapturePoint(
type: captureType,
type: _captureType,
uuid: _uuid,
rotationX: _rotX,
rotationY: _rotY,
rotationZ: _rotZ,
accelerationX: accelerations[0],
accelerationY: accelerations[1],
accelerationZ: accelerations[2],
millisecondsSinceEpoch: DateTime.now().toUtc().millisecondsSinceEpoch,
accelerationX: _accX,
accelerationY: _accY,
accelerationZ: _accZ,
millisecondsSinceEpochReceived:
DateTime.now().toUtc().millisecondsSinceEpoch,
millisecondsSinceEpochSend: _millisecondsSinceEpochSend,
);
emit(state.copyWith(lastCapturedPoint: capturePoint));
debugPrint("UPDATED POINT!");
// _writeToObjectBox(capturePoint: capturePoint);
gotRotationX = false;
gotRotationY = false;
gotRotationZ = false;
if (isRecording) {
_writeToObjectBox(capturePoint: capturePoint);
}
gotAcceleration = false;
gotRotation = false;
gotTimeStamp = false;
}
});
await setCapturingOn();
await setTime(DateTime.now().millisecondsSinceEpoch);
device.cancelWhenDisconnected(dataCapturingSubscription);
await senseService.setNotifyValue(true);
await _senseService.setNotifyValue(true);
emit(state.copyWith(status: XiaoConnectorStatus.capturing));
}
@ -167,28 +223,39 @@ class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
Future<void> stopCapturing() async {
await dataCapturingSubscription.cancel();
await _senseService.setNotifyValue(false);
emit(state.copyWith(status: XiaoConnectorStatus.connected));
}
void toggleRecording({required String captureType}) {
isRecording = !isRecording;
if (isRecording) {
_captureType = captureType;
_uuid = const Uuid().v4();
_captureBoxes.put(
CaptureBox(
type: _captureType,
uuid: _uuid,
),
);
}
}
void _writeToObjectBox({
required CapturePoint capturePoint,
}) {
_capturePointsBox.put(capturePoint);
}
void readFromObjectBox() {
final points = _capturePointsBox.getAll();
for (var i = 0; i < points.length; i++) {
final point = points[i];
debugPrint('Type: ${point.type}');
debugPrint('Id: ${point.id}');
debugPrint('RotX: ${point.rotationX}');
debugPrint('RotY: ${point.rotationY}');
debugPrint('RotZ: ${point.rotationZ}');
debugPrint('AccelX: ${point.accelerationX}');
debugPrint('AccelY: ${point.accelerationY}');
debugPrint('AccelZ: ${point.accelerationZ}');
debugPrint('Date: ${point.millisecondsSinceEpoch}');
}
List<CapturePoint> getCapturePointsOfUuid(String uuid) {
final query =
_capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build();
final points = query.find();
return points;
}
List<CaptureBox> getAllCaptureBoxes() => _captureBoxes.getAll();
List<CapturePoint> getAllCapturePoints() => _capturePointsBox.getAll();
}

View File

@ -0,0 +1,14 @@
import 'package:objectbox/objectbox.dart';
@Entity()
class CaptureBox {
CaptureBox({
required this.type,
required this.uuid,
});
@Id()
int id = 0;
String type;
String uuid;
}

View File

@ -4,25 +4,30 @@ import 'package:objectbox/objectbox.dart';
class CapturePoint {
CapturePoint({
required this.type,
required this.uuid,
required this.rotationX,
required this.rotationY,
required this.rotationZ,
required this.accelerationX,
required this.accelerationY,
required this.accelerationZ,
this.millisecondsSinceEpoch,
required this.millisecondsSinceEpochReceived,
required this.millisecondsSinceEpochSend,
});
@Id()
int id = 0;
String type;
String rotationX;
String rotationY;
String rotationZ;
String accelerationX;
String accelerationY;
String accelerationZ;
String uuid;
int rotationX;
int rotationY;
int rotationZ;
int accelerationX;
int accelerationY;
int accelerationZ;
// @Property(type: PropertyType.date)
int? millisecondsSinceEpoch;
int millisecondsSinceEpochReceived;
int millisecondsSinceEpochSend;
}

View File

@ -0,0 +1,32 @@
import 'dart:typed_data';
int fromBytesToInt32(int b3, int b2, int b1, int b0) {
final int8List = Int8List(4)
..[3] = b3
..[2] = b2
..[1] = b1
..[0] = b0;
return int8List.buffer.asByteData().getInt32(0);
}
int fromBytesToInt64(
int b7,
int b6,
int b5,
int b4,
int b3,
int b2,
int b1,
int b0,
) {
final int8List = Int8List(8)
..[7] = b7
..[6] = b6
..[5] = b5
..[4] = b4
..[3] = b3
..[2] = b2
..[1] = b1
..[0] = b0;
return int8List.buffer.asByteData().getInt64(0);
}

View File

@ -1,46 +1,193 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:xiao_pet_tracker/app_router/app_router.dart';
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
class CaptureView extends StatelessWidget {
class CaptureView extends StatefulWidget {
const CaptureView({super.key});
@override
State<CaptureView> createState() => _CaptureViewState();
}
class _CaptureViewState extends State<CaptureView> {
late TextEditingController _controller;
bool _isTextFieldFocused = false;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// final count = context.select((XiaoConnectorCubit cubit) => cubit.state);
final lastCapturePoint = context
.select((XiaoConnectorCubit cubit) => cubit.state.lastCapturedPoint);
return Scaffold(
appBar: AppBar(
title: const Text('Capturing'),
title: const Text('Live Feed'),
),
body: SingleChildScrollView(
child: Center(
child: Column(
children: [
const SizedBox(
height: 8,
),
ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().stopCapturing();
},
child: const Text('Stop Capturing'),
child: const Text('Close Live Feed'),
),
const SizedBox(
height: 12,
),
const Text(
'Last Captured Point',
style: TextStyle(fontWeight: FontWeight.bold),
),
const Divider(
indent: 80,
endIndent: 80,
),
const Text(
'Received Time',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
'${DateTime.fromMillisecondsSinceEpoch(lastCapturePoint?.millisecondsSinceEpoch ?? 0)}'),
const Divider(),
'${DateTime.fromMillisecondsSinceEpoch(
lastCapturePoint?.millisecondsSinceEpochReceived ?? 0,
)}',
),
const Text(
'Send Time',
style: TextStyle(fontWeight: FontWeight.bold),
),
const Text('(time the controller send the data)'),
Text(
'${DateTime.fromMillisecondsSinceEpoch(
lastCapturePoint?.millisecondsSinceEpochSend ?? 0,
)}',
),
const SizedBox(
height: 12,
),
ElevatedButton(
onPressed: () {
// context.read<XiaoConnectorCubit>().startCapturing(
// captureType: 'test',
// );
final timeToSend = DateTime.now().millisecondsSinceEpoch;
context.read<XiaoConnectorCubit>().setTime(timeToSend);
},
child: const Text('Send new Timestamp'),
),
const SizedBox(
height: 12,
),
const Divider(
indent: 80,
endIndent: 80,
),
Text('Acceleration X: ${lastCapturePoint?.accelerationX}'),
Text('Acceleration Y: ${lastCapturePoint?.accelerationY}'),
Text('Acceleration Z: ${lastCapturePoint?.accelerationZ}'),
const Divider(),
const Divider(
indent: 80,
endIndent: 80,
),
Text('Rotation X: ${lastCapturePoint?.rotationX}'),
Text('Rotation Y: ${lastCapturePoint?.rotationY}'),
Text('Rotation Z: ${lastCapturePoint?.rotationZ}'),
const SizedBox(
height: 16,
),
Padding(
padding: const EdgeInsets.only(left: 32, right: 32, top: 16),
child: FocusScope(
child: Focus(
onFocusChange: (focus) {
_isTextFieldFocused = focus;
setState(() {});
},
child: TextField(
controller: _controller,
decoration: const InputDecoration(
label: Text('Capture Name'),
),
),
),
),
),
const SizedBox(
height: 16,
)
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: _isTextFieldFocused
? null
: FloatingActionButton.large(
child: context.read<XiaoConnectorCubit>().isRecording
? const Stack(
children: [
Align(
child: Icon(Icons.pause),
),
Align(
child: SizedBox.expand(
child: Padding(
padding: EdgeInsets.all(12),
child: CircularProgressIndicator(),
),
),
),
],
)
: const Icon(Icons.play_arrow),
onPressed: () {
if (_controller.value.text == '') {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
'Please enter Capture Name',
),
content: const Text(
'You need to enter a capture name before you can start capturing data.',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Ok',
),
),
],
);
},
);
} else {
context.read<XiaoConnectorCubit>().toggleRecording(
captureType: _controller.value.text,
);
}
},
),
);
}
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
class ConnectedView extends StatelessWidget {
const ConnectedView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Xiao Connector'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'''
You are connected to the Xiao Sense.
Now you can open the live feed.''',
textAlign: TextAlign.center,
),
const SizedBox(
height: 8,
),
ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().startCapturing();
// context.read<XiaoConnectorCubit>().setCapturingOn();
},
child: const Text('Open Live Feed'),
),
],
),
),
);
}
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
class InitialView extends StatelessWidget {
const InitialView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Xiao Connector'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'''
You are currently not connected to the Xiao Sense.
Click on `Connect` to try to connect to the device.''',
textAlign: TextAlign.center,
),
const SizedBox(
height: 8,
),
ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().connect();
},
child: const Text('Connect'),
),
],
),
),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class LoadingView extends StatelessWidget {
const LoadingView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
}

View File

@ -3,7 +3,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:xiao_pet_tracker/xiao_connector/cubit/xiao_connector_cubit.dart';
import 'package:xiao_pet_tracker/xiao_connector/view/capture_view.dart';
import 'package:xiao_pet_tracker/xiao_connector/view/connected_view.dart';
import 'package:xiao_pet_tracker/xiao_connector/view/failure_view.dart';
import 'package:xiao_pet_tracker/xiao_connector/view/initial_view.dart';
import 'package:xiao_pet_tracker/xiao_connector/view/loading_view.dart';
@RoutePage()
class XiaoConnectorPage extends StatelessWidget {
@ -24,31 +27,9 @@ class XiaoConnectorView extends StatelessWidget {
body: BlocBuilder<XiaoConnectorCubit, XiaoConnectorState>(
builder: (context, state) {
return switch (state.status) {
XiaoConnectorStatus.initial => ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().connect();
},
child: const Text('Connect'),
),
XiaoConnectorStatus.loading => const CircularProgressIndicator(),
XiaoConnectorStatus.connected => Column(
children: [
ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().startCapturing(
captureType: 'test',
);
},
child: const Text('Start Capturing'),
),
ElevatedButton(
onPressed: () {
context.read<XiaoConnectorCubit>().readFromObjectBox();
},
child: const Text('Get Points'),
),
],
),
XiaoConnectorStatus.initial => const InitialView(),
XiaoConnectorStatus.loading => const LoadingView(),
XiaoConnectorStatus.connected => const ConnectedView(),
XiaoConnectorStatus.capturing => const CaptureView(),
XiaoConnectorStatus.failure => const FailureView(),
};

View File

@ -202,10 +202,10 @@ packages:
dependency: transitive
description:
name: coverage
sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268"
sha256: "4b03e11f6d5b8f6e5bb5e9f7889a56fe6c5cbe942da5378ea4d4d7f73ef9dfe5"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
crypto:
dependency: transitive
description:
@ -214,6 +214,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.6"
csv:
dependency: "direct main"
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
dart_style:
dependency: transitive
description:
@ -303,18 +311,18 @@ packages:
dependency: transitive
description:
name: flutter_blue_plus
sha256: "22912f71d140ff5c15735d4b2f0425e0a5f61ce6444a29360e7fca05eeebbad7"
sha256: "45ccee4a838f321c301b6130fbf3de28f07a2d6334a69eb5e13b6f83683080e0"
url: "https://pub.dev"
source: hosted
version: "1.33.5"
version: "1.33.6"
flutter_blue_plus_windows:
dependency: "direct main"
description:
name: flutter_blue_plus_windows
sha256: "7ec7ff439e37d00640a5e74dfcdea61bc338c430a8a7b9e29e9a1468108d2a14"
sha256: "8e84f894da4e41e776776f0e706cc35a237862a4d883033464c793b9d38de116"
url: "https://pub.dev"
source: hosted
version: "1.24.15"
version: "1.24.20"
flutter_localizations:
dependency: "direct main"
description: flutter
@ -324,10 +332,10 @@ packages:
dependency: transitive
description:
name: flutter_svg
sha256: "1b7723a814d84fb65869ea7115cdb3ee7c3be5a27a755c1ec60e049f6b9fcbb2"
sha256: "578bd8c508144fdaffd4f77b8ef2d8c523602275cd697cc3db284dbd762ef4ce"
url: "https://pub.dev"
source: hosted
version: "2.0.11"
version: "2.0.14"
flutter_test:
dependency: "direct dev"
description: flutter
@ -606,10 +614,10 @@ packages:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.1.0"
path_provider:
dependency: "direct main"
description:
@ -839,6 +847,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
@ -935,30 +951,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "0b9149c6ddb013818075b072b9ddc1b89a5122fff1275d4648d297086b46c4f0"
sha256: "773c9522d66d523e1c7b25dfb95cc91c26a1e17b107039cfe147285e92de7878"
url: "https://pub.dev"
source: hosted
version: "1.1.12"
version: "1.1.14"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.12"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: f3b9b6e4591c11394d4be4806c63e72d3a41778547b2c1e2a8a04fadcfd7d173
sha256: ab9ff38fc771e9ee1139320adbe3d18a60327370c218c60752068ebee4b49ab1
url: "https://pub.dev"
source: hosted
version: "1.1.12"
version: "1.1.15"
vector_math:
dependency: transitive
description:

View File

@ -1,6 +1,6 @@
name: xiao_pet_tracker
description: A Very Good Project created by Very Good CLI.
version: 1.0.0+1
version: 1.0.0+3
publish_to: none
environment:
@ -10,6 +10,7 @@ dependencies:
auto_route: ^9.2.2
beacon_distance: ^0.0.3
bloc: ^8.1.4
csv: ^6.0.0
ditredi: ^2.0.2
equatable: ^2.0.5
flutter:
@ -27,6 +28,7 @@ dependencies:
path: ^1.9.0
path_provider: ^2.1.5
permission_handler: ^11.3.1
uuid: ^4.5.1
dev_dependencies:
auto_route_generator: ^9.0.0

View File

@ -1,4 +1,4 @@
module blinky
module xiao-pet-tracker
go 1.23.1

View File

@ -4,38 +4,31 @@ import (
"encoding/binary"
"fmt"
"machine"
"math/rand"
"time"
"tinygo.org/x/bluetooth"
"tinygo.org/x/drivers/lsm6ds3tr"
)
// TinyGo Drivers Docs:
// https://github.com/tinygo-org/drivers
// TinyGo Bluetooth Docs:
// https://github.com/tinygo-org/bluetooth
var adapter = bluetooth.DefaultAdapter
var ledColor = [3]byte{0x00, 0x00, 0xff}
var leds = [3]machine.Pin{machine.LED_RED, machine.LED_GREEN, machine.LED_BLUE}
var hasColorChange = true
var isBleConnected bool = false
var senseAccelerationData string = "-0.000 -0.000 -0.000"
var senseRotationData string = "-0.000 -0.000 -0.000"
var bleConnected bool = false
var isCapturing bool = false
var (
LSM6DS3TRService = [16]byte{0x4C, 0x53, 0x4D, 0x36, 0x44, 0x53, 0x33, 0x54, 0x52, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
accelerationData = [16]byte{0x61, 0x63, 0x63, 0x65, 0x6C, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x44, 0x61, 0x74, 0x61}
tempSenseService = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x53, 0x65, 0x6E, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
temperatureSense = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x6E, 0x73, 0x65}
unixTimeStampRst = [16]byte{0x75, 0x6E, 0x69, 0x78, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x52, 0x73, 0x74}
capturingService = [16]byte{0x63, 0x61, 0x70, 0x74, 0x75, 0x72, 0x69, 0x6E, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
//tempSenseService = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x53, 0x65, 0x6E, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
//temperatureSense = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x6E, 0x73, 0x65}
)
var unixTimeStamp time.Time = time.Now()
const sleepDuration time.Duration = time.Millisecond * 100
func main() {
// Configure LSM6DS3TR
machine.I2C0.Configure(machine.I2CConfig{})
@ -48,10 +41,6 @@ func main() {
time.Sleep(time.Second)
}
}
//
// for _, led := range leds {
// led.Configure(machine.PinConfig{Mode: machine.PinOutput})
// }
// Configure Bluetooth
must("enable BLE stack", adapter.Enable())
@ -68,23 +57,10 @@ func main() {
adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) {
if connected {
println("connected, not advertising...")
// leds[0].Low()
// leds[1].High()
// ledColor[0] = 0
// ledColor[1] = 1
// ledColor[2] = 0
// hasColorChange = true
bleConnected = true
isBleConnected = true
} else {
println("disconnected, advertising...")
// leds[0].High()
// leds[1].Low()
// ledColor[0] = 0
// ledColor[1] = 0
// ledColor[2] = 1
// hasColorChange = true
bleConnected = false
isBleConnected = false
isCapturing = false
}
})
//
@ -98,9 +74,35 @@ func main() {
{
Handle: &senseCharacteristic,
UUID: bluetooth.NewUUID(accelerationData),
Value: []byte(senseAccelerationData),
// can only send a max amount of 20 bytes in one packet
//Value: []byte{},
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
},
{
UUID: bluetooth.NewUUID(unixTimeStampRst),
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
if len(value) != 8 {
return
}
millisFromEpoch := binary.BigEndian.Uint64(value)
unixTimeStamp = time.Unix(0, int64(millisFromEpoch)*int64(time.Millisecond))
},
},
{
UUID: bluetooth.NewUUID(capturingService),
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
if len(value) != 1 {
return
}
if value[0] == 1 {
isCapturing = true
} else {
isCapturing = false
}
},
},
},
}))
@ -119,55 +121,66 @@ func main() {
// Main Loop
for {
time.Sleep(100 * time.Millisecond)
// for !hasColorChange {
// for i, led := range leds {
// led.Set(ledColor[i] == 0)
// }
// time.Sleep(10 * time.Millisecond)
// }
// hasColorChange = false
// Only read and update sensor data
// with an active bluetooth connection
if bleConnected {
// and when `isCapturing` is set to true
if isBleConnected {
X, Y, Z, _ := accel.ReadRotation()
rotX := fmt.Sprintf("rX=%f", float32(X)/100000000)
rotY := fmt.Sprintf("rY=%f", float32(Y)/100000000)
rotZ := fmt.Sprintf("rZ=%f", float32(Z)/100000000)
x, y, z, _ := accel.ReadAcceleration()
accel := fmt.Sprintf("%.3f,%.3f,%.3f", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
//senseAccelerationData = dataTest
//tempTest, _ := accel.ReadTemperature()
//tempTestBytes := []byte{}
//tempData = fmt.Sprintf("%i", tempTest)
arrRot := valuesToByteArray(X, Y, Z, int8(1))
senseCharacteristic.Write(arrRot)
//senseCharacteristic.Write([]byte(dataRotation))
//senseCharacteristic.Write([]byte("-0.0,-1.2,-5.6"))
senseCharacteristic.Write([]byte(rotX))
senseCharacteristic.Write([]byte(rotY))
senseCharacteristic.Write([]byte(rotZ))
senseCharacteristic.Write([]byte(accel))
arrAcc := valuesToByteArray(x, y, z, int8(2))
senseCharacteristic.Write(arrAcc)
// bs := make([]byte, 4)
// binary.LittleEndian.PutUint32(bs, X)
arrTime := timeStampToByteArray(unixTimeStamp.UnixMilli(), int8(3))
senseCharacteristic.Write(arrTime)
}
//binary.LittleEndian.AppendUint32(tempTestBytes, uint32(tempTest))
//tempCharacteristic.Write(tempTestBytes)
fmt.Println("TIME: ", unixTimeStamp)
time.Sleep(sleepDuration)
if isCapturing {
unixTimeStamp = unixTimeStamp.Add(sleepDuration)
}
}
}
func valuesToByteArray(x int32, y int32, z int32, p int8) []byte {
arr := make([]byte, 13)
arr[0] = byte(x)
arr[1] = byte(x >> 8)
arr[2] = byte(x >> 16)
arr[3] = byte(x >> 24)
arr[4] = byte(y)
arr[5] = byte(y >> 8)
arr[6] = byte(y >> 16)
arr[7] = byte(y >> 24)
arr[8] = byte(z)
arr[9] = byte(z >> 8)
arr[10] = byte(z >> 16)
arr[11] = byte(z >> 24)
arr[12] = byte(p)
return arr
}
func timeStampToByteArray(value int64, p int8) []byte {
arr := make([]byte, 9)
arr[0] = byte(value)
arr[1] = byte(value >> 8)
arr[2] = byte(value >> 16)
arr[3] = byte(value >> 24)
arr[4] = byte(value >> 32)
arr[5] = byte(value >> 40)
arr[6] = byte(value >> 48)
arr[7] = byte(value >> 56)
arr[8] = byte(p)
return arr
}
func must(action string, err error) {
if err != nil {
panic("failed to " + action + ": " + err.Error())
}
}
// Returns an int >= min, < max
func randomInt(min, max int) uint8 {
return uint8(min + rand.Intn(max-min))
}

View File

@ -0,0 +1,106 @@
package main
// import (
// "encoding/binary"
// "fmt"
// "unsafe"
// )
// func main() {
// const x int32 = 2017403647
// const y int32 = 1010101010
// const z int32 = 2020202020
// // bytearr := valuesToByteArrayTest(x, y, z, 1)
// // fmt.Println("TEST: ", bytearr)
// // slice := binary.LittleEndian.Uint32(bytearr[0:4])
// // fmt.Println("SLICE:", slice)
// // slice2 := binary.LittleEndian.Uint32(bytearr[4:8])
// // fmt.Println("SLICE2:", slice2)
// bs := make([]byte, 4)
// binary.LittleEndian.PutUint32(bs, uint32(y))
// fmt.Println(bs)
// // buf := make([]byte, 4)
// // arr[0] = byte(x >> 8)
// // buf[0], buf[1], buf[2] = uint8(7676767>>16), uint8(id>>8), uint8(id)
// bytearr := valuesToByteArray3(x, y, z, 1)
// fmt.Println("TEST: ", bytearr)
// slice := binary.LittleEndian.Uint32(bytearr[0:4])
// fmt.Println("SLICE:", slice)
// slice2 := binary.LittleEndian.Uint32(bytearr[4:8])
// fmt.Println("SLICE2:", slice2)
// }
// func valuesToByteArrayOld(x int32, y int32, z int32, p int8) []byte {
// arr := make([]byte, 13)
// for i := 0; i < 12; i++ {
// var test unsafe.Pointer
// var test2 unsafe.Pointer
// var test3 unsafe.Pointer
// if i < 4 {
// test = unsafe.Pointer(&x)
// } else if i < 8 {
// test2 = unsafe.Pointer(&y)
// } else if i < 12 {
// test3 = unsafe.Pointer(&z)
// }
// byt := *(*uint8)(unsafe.Pointer(uintptr(test) + uintptr(i)))
// arr[i] = byt
// }
// arr[12] = byte(p)
// return arr
// }
// func valuesToByteArray1(x int32, y int32, z int32, p int8) []byte {
// arr := make([]byte, 13)
// arr[0] = byte(x)
// arr[1] = byte(x >> 8)
// arr[2] = byte(x >> 16)
// arr[3] = byte(x >> 24)
// arr[4] = byte(y)
// arr[5] = byte(y >> 8)
// arr[6] = byte(y >> 16)
// arr[7] = byte(y >> 24)
// arr[8] = byte(z)
// arr[9] = byte(z >> 8)
// arr[10] = byte(z >> 16)
// arr[11] = byte(z >> 24)
// arr[12] = byte(p)
// return arr
// }
// func valuesToByteArray2(x int32, y int32, z int32, p int8) []byte {
// buf := make([]byte, 13)
// binary.LittleEndian.PutUint32(buf[0:], uint32(x))
// binary.LittleEndian.PutUint32(buf[4:], uint32(y))
// binary.LittleEndian.PutUint32(buf[8:], uint32(z))
// buf[12] = byte(p)
// return buf
// }
// func valuesToByteArray3(x int32, y int32, z int32, p int8) []byte {
// arr := make([]byte, 13)
// for i := 0; i < 12; i++ {
// var test unsafe.Pointer
// if i < 4 {
// test = unsafe.Pointer(&x)
// } else if i < 8 {
// test = unsafe.Pointer(&y)
// } else if i < 12 {
// test = unsafe.Pointer(&z)
// }
// byt := *(*uint8)(unsafe.Pointer(uintptr(test) + uintptr(i)))
// arr[i] = byt
// }
// arr[12] = byte(p)
// return arr
// }