diff --git a/android/.classpath b/android/.classpath index aa556df..3589094 100644 --- a/android/.classpath +++ b/android/.classpath @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/android/build.gradle b/android/build.gradle index 4a23601..00d9a35 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,5 @@ plugins { id 'com.android.library' - id 'org.jetbrains.kotlin.android' } allprojects { @@ -17,18 +16,18 @@ android { namespace 'im.nfc.flutter_nfc_kit' - compileSdkVersion 33 - + compileSdkVersion 36 + compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { - minSdkVersion 19 + minSdkVersion 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -36,8 +35,11 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KotlinVersion" - implementation "org.jetbrains.kotlin:kotlin-reflect:$KotlinVersion" - implementation 'androidx.core:core-ktx:1.10.1' } diff --git a/android/gradle.properties b/android/gradle.properties index 39a8970..bdcc437 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -AGPVersion=7.0.4 -KotlinVersion=1.9.10 \ No newline at end of file +AGPVersion=9.2.0 +KotlinVersion=2.3.21 \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000..d997cfc Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 4a16027..c61a118 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Fri Sep 08 22:01:14 CST 2023 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..739907d --- /dev/null +++ b/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 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 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/2d6327017519d23b96af35865dc997fcb544fb40/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/. +# +############################################################################## + +# Attempt to set APP_HOME + +# 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\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +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 +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + 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" && ! "$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 + +# 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. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + 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, 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" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# 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" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..e509b2d --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,93 @@ +@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 +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +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% equ 0 goto execute + +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 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +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 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +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! +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 + +:omega diff --git a/android/settings.gradle b/android/settings.gradle index b30c81d..8813f0c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -5,8 +5,7 @@ pluginManagement { mavenCentral() } plugins { - id 'com.android.library' version "${AGPVersion}" - id 'org.jetbrains.kotlin.android' version "${KotlinVersion}" + id 'com.android.library' version "${AGPVersion}" apply false } } diff --git a/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt b/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt index 6870842..1b29f8b 100644 --- a/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt +++ b/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt @@ -43,6 +43,8 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private var ndefTechnology: Ndef? = null private var mifareInfo: MifareInfo? = null + private var onUserCancelled: (() -> Unit)? = null + private fun TagTechnology.transceive(data: ByteArray, timeout: Int?): ByteArray { if (timeout != null) { try { @@ -119,7 +121,10 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } "finish" -> { - pollingTimeoutTask?.cancel() + val isPolling = pollingTimeoutTask?.cancel() + if (isPolling == true) { + onUserCancelled?.invoke() + } thread { try { val tagTech = tagTechnology @@ -427,7 +432,10 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } override fun onDetachedFromActivity() { - pollingTimeoutTask?.cancel() + val isPolling = pollingTimeoutTask?.cancel() + if (isPolling == true) { + onUserCancelled?.invoke() + } pollingTimeoutTask = null tagTechnology = null ndefTechnology = null @@ -444,9 +452,18 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { if (activity.get() != null) { nfcAdapter.disableReaderMode(activity.get()) } + onUserCancelled = null result.error("408", "Polling tag timeout", null) } + onUserCancelled = { + if (activity.get() != null) { + nfcAdapter.disableReaderMode(activity.get()) + } + onUserCancelled = null + result.error("600", "User cancelled", null) + } + nfcAdapter.enableReaderMode(activity.get(), { tag -> pollingTimeoutTask?.cancel() diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e8a319..d306bd1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -23,7 +23,7 @@ void main() { class MyApp extends StatefulWidget { @override - _MyAppState createState() => _MyAppState(); + State createState() => _MyAppState(); } class _MyAppState extends State with SingleTickerProviderStateMixin { @@ -43,13 +43,13 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { @override void initState() { super.initState(); - if (!kIsWeb) - _platformVersion = - '${Platform.operatingSystem} ${Platform.operatingSystemVersion}'; - else + if (!kIsWeb) { + _platformVersion = '${Platform.operatingSystem} ${Platform.operatingSystemVersion}'; + } else { _platformVersion = 'Web'; + } initPlatformState(); - _tabController = new TabController(length: 2, vsync: this); + _tabController = TabController(length: 2, vsync: this); _records = []; } @@ -86,222 +86,201 @@ class _MyAppState extends State with SingleTickerProviderStateMixin { ], controller: _tabController, )), - body: new TabBarView(controller: _tabController, children: [ + body: TabBarView(controller: _tabController, children: [ Scrollbar( child: SingleChildScrollView( child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 20), - Text('Running on: $_platformVersion\nNFC: $_availability'), - const SizedBox(height: 10), - ElevatedButton( - onPressed: () async { - try { - NFCTag tag = await FlutterNfcKit.poll(); - setState(() { - _tag = tag; - }); - await FlutterNfcKit.setIosAlertMessage( - "Working on it..."); - _mifareResult = null; - if (tag.standard == "ISO 14443-4 (Type B)") { - String result1 = - await FlutterNfcKit.transceive("00B0950000"); - String result2 = await FlutterNfcKit.transceive( - "00A4040009A00000000386980701"); - setState(() { - _result = '1: $result1\n2: $result2\n'; - }); - } else if (tag.type == NFCTagType.iso18092) { - String result1 = - await FlutterNfcKit.transceive("060080080100"); - setState(() { - _result = '1: $result1\n'; - }); - } else if (tag.ndefAvailable ?? false) { - var ndefRecords = await FlutterNfcKit.readNDEFRecords(); - var ndefString = ''; - for (int i = 0; i < ndefRecords.length; i++) { - ndefString += '${i + 1}: ${ndefRecords[i]}\n'; - } - setState(() { - _result = ndefString; - }); - } else if (tag.type == NFCTagType.webusb) { - var r = await FlutterNfcKit.transceive( - "00A4040006D27600012401"); - print(r); - } - } catch (e) { - setState(() { - _result = 'error: $e'; - }); + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + const SizedBox(height: 20), + Text('Running on: $_platformVersion\nNFC: $_availability'), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () async { + try { + NFCTag tag = await FlutterNfcKit.poll(); + setState(() { + _tag = tag; + }); + await FlutterNfcKit.setIosAlertMessage("Working on it..."); + _mifareResult = null; + if (tag.standard == "ISO 14443-4 (Type B)") { + String result1 = await FlutterNfcKit.transceive("00B0950000"); + String result2 = await FlutterNfcKit.transceive("00A4040009A00000000386980701"); + setState(() { + _result = '1: $result1\n2: $result2\n'; + }); + } else if (tag.type == NFCTagType.iso18092) { + String result1 = await FlutterNfcKit.transceive("060080080100"); + setState(() { + _result = '1: $result1\n'; + }); + } else if (tag.ndefAvailable ?? false) { + var ndefRecords = await FlutterNfcKit.readNDEFRecords(); + var ndefString = ''; + for (int i = 0; i < ndefRecords.length; i++) { + ndefString += '${i + 1}: ${ndefRecords[i]}\n'; } + setState(() { + _result = ndefString; + }); + } else if (tag.type == NFCTagType.webusb) { + var r = await FlutterNfcKit.transceive("00A4040006D27600012401"); + print(r); + } + } catch (e) { + setState(() { + _result = 'error: $e'; + }); + } - // Pretend that we are working - if (!kIsWeb) sleep(new Duration(seconds: 1)); - await FlutterNfcKit.finish(iosAlertMessage: "Finished!"); - }, - child: Text('Start polling'), - ), - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: _tag != null - ? Text( - 'ID: ${_tag!.id}\nStandard: ${_tag!.standard}\nType: ${_tag!.type}\nATQA: ${_tag!.atqa}\nSAK: ${_tag!.sak}\nHistorical Bytes: ${_tag!.historicalBytes}\nProtocol Info: ${_tag!.protocolInfo}\nApplication Data: ${_tag!.applicationData}\nHigher Layer Response: ${_tag!.hiLayerResponse}\nManufacturer: ${_tag!.manufacturer}\nSystem Code: ${_tag!.systemCode}\nDSF ID: ${_tag!.dsfId}\nNDEF Available: ${_tag!.ndefAvailable}\nNDEF Type: ${_tag!.ndefType}\nNDEF Writable: ${_tag!.ndefWritable}\nNDEF Can Make Read Only: ${_tag!.ndefCanMakeReadOnly}\nNDEF Capacity: ${_tag!.ndefCapacity}\nMifare Info:${_tag!.mifareInfo} Transceive Result:\n$_result\n\nBlock Message:\n$_mifareResult') - : const Text('No tag polled yet.')), - ])))), + // Pretend that we are working + if (!kIsWeb) sleep(Duration(seconds: 1)); + await FlutterNfcKit.finish(iosAlertMessage: "Finished!"); + }, + child: Text('Start polling'), + ), + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: _tag != null + ? Text( + 'ID: ${_tag!.id}\nStandard: ${_tag!.standard}\nType: ${_tag!.type}\nATQA: ${_tag!.atqa}\nSAK: ${_tag!.sak}\nHistorical Bytes: ${_tag!.historicalBytes}\nProtocol Info: ${_tag!.protocolInfo}\nApplication Data: ${_tag!.applicationData}\nHigher Layer Response: ${_tag!.hiLayerResponse}\nManufacturer: ${_tag!.manufacturer}\nSystem Code: ${_tag!.systemCode}\nDSF ID: ${_tag!.dsfId}\nNDEF Available: ${_tag!.ndefAvailable}\nNDEF Type: ${_tag!.ndefType}\nNDEF Writable: ${_tag!.ndefWritable}\nNDEF Can Make Read Only: ${_tag!.ndefCanMakeReadOnly}\nNDEF Capacity: ${_tag!.ndefCapacity}\nMifare Info:${_tag!.mifareInfo} Transceive Result:\n$_result\n\nBlock Message:\n$_mifareResult') + : const Text('No tag polled yet.')), + ])))), Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, + child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [ + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ElevatedButton( - onPressed: () async { - if (_records!.length != 0) { - try { - NFCTag tag = await FlutterNfcKit.poll(); - setState(() { - _tag = tag; - }); - if (tag.type == NFCTagType.mifare_ultralight || - tag.type == NFCTagType.mifare_classic || - tag.type == NFCTagType.iso15693) { - await FlutterNfcKit.writeNDEFRecords(_records!); - setState(() { - _writeResult = 'OK'; - }); - } else { - setState(() { - _writeResult = - 'error: NDEF not supported: ${tag.type}'; - }); - } - } catch (e, stacktrace) { - setState(() { - _writeResult = 'error: $e'; - }); - print(stacktrace); - } finally { - await FlutterNfcKit.finish(); - } + ElevatedButton( + onPressed: () async { + if (_records!.isNotEmpty) { + try { + NFCTag tag = await FlutterNfcKit.poll(); + setState(() { + _tag = tag; + }); + if (tag.type == NFCTagType.mifare_ultralight || + tag.type == NFCTagType.mifare_classic || + tag.type == NFCTagType.iso15693) { + await FlutterNfcKit.writeNDEFRecords(_records!); + setState(() { + _writeResult = 'OK'; + }); } else { setState(() { - _writeResult = 'error: No record'; + _writeResult = 'error: NDEF not supported: ${tag.type}'; }); } - }, - child: Text("Start writing"), - ), - ElevatedButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return SimpleDialog( - title: Text("Record Type"), - children: [ - SimpleDialogOption( - child: Text("Text Record"), - onPressed: () async { - Navigator.pop(context); - final result = await Navigator.push( - context, MaterialPageRoute( - builder: (context) { - return NDEFTextRecordSetting(); - })); - if (result != null) { - if (result is ndef.TextRecord) { - setState(() { - _records!.add(result); - }); - } - } - }, - ), - SimpleDialogOption( - child: Text("Uri Record"), - onPressed: () async { - Navigator.pop(context); - final result = await Navigator.push( - context, MaterialPageRoute( - builder: (context) { - return NDEFUriRecordSetting(); - })); - if (result != null) { - if (result is ndef.UriRecord) { - setState(() { - _records!.add(result); - }); - } - } - }, - ), - SimpleDialogOption( - child: Text("Raw Record"), - onPressed: () async { - Navigator.pop(context); - final result = await Navigator.push( - context, MaterialPageRoute( - builder: (context) { - return NDEFRecordSetting(); - })); - if (result != null) { - if (result is ndef.NDEFRecord) { - setState(() { - _records!.add(result); - }); - } - } - }, - ), - ]); - }); - }, - child: Text("Add record"), - ) - ], + } catch (e, stacktrace) { + setState(() { + _writeResult = 'error: $e'; + }); + print(stacktrace); + } finally { + await FlutterNfcKit.finish(); + } + } else { + setState(() { + _writeResult = 'error: No record'; + }); + } + }, + child: Text("Start writing"), ), - const SizedBox(height: 10), - Text('Result: $_writeResult'), - const SizedBox(height: 10), - Expanded( - flex: 1, - child: ListView( - shrinkWrap: true, - children: List.generate( - _records!.length, - (index) => GestureDetector( - child: Padding( - padding: const EdgeInsets.all(10), - child: Text( - 'id:${_records![index].idString}\ntnf:${_records![index].tnf}\ntype:${_records![index].type?.toHexString()}\npayload:${_records![index].payload?.toHexString()}\n')), - onTap: () async { - final result = await Navigator.push(context, - MaterialPageRoute(builder: (context) { - return NDEFRecordSetting( - record: _records![index]); - })); - if (result != null) { - if (result is ndef.NDEFRecord) { - setState(() { - _records![index] = result; - }); - } else if (result is String && - result == "Delete") { - _records!.removeAt(index); - } + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return SimpleDialog(title: Text("Record Type"), children: [ + SimpleDialogOption( + child: Text("Text Record"), + onPressed: () async { + Navigator.pop(context); + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) { + return NDEFTextRecordSetting(); + })); + if (result != null) { + if (result is ndef.TextRecord) { + setState(() { + _records!.add(result); + }); } - }, - ))), - ), - ]), + } + }, + ), + SimpleDialogOption( + child: Text("Uri Record"), + onPressed: () async { + Navigator.pop(context); + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) { + return NDEFUriRecordSetting(); + })); + if (result != null) { + if (result is ndef.UriRecord) { + setState(() { + _records!.add(result); + }); + } + } + }, + ), + SimpleDialogOption( + child: Text("Raw Record"), + onPressed: () async { + Navigator.pop(context); + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) { + return NDEFRecordSetting(); + })); + if (result != null) { + if (result is ndef.NDEFRecord) { + setState(() { + _records!.add(result); + }); + } + } + }, + ), + ]); + }); + }, + child: Text("Add record"), + ) + ], + ), + const SizedBox(height: 10), + Text('Result: $_writeResult'), + const SizedBox(height: 10), + Expanded( + flex: 1, + child: ListView( + shrinkWrap: true, + children: List.generate( + _records!.length, + (index) => GestureDetector( + child: Padding( + padding: const EdgeInsets.all(10), + child: Text( + 'id:${_records![index].idString}\ntnf:${_records![index].tnf}\ntype:${_records![index].type?.toHexString()}\npayload:${_records![index].payload?.toHexString()}\n')), + onTap: () async { + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) { + return NDEFRecordSetting(record: _records![index]); + })); + if (result != null) { + if (result is ndef.NDEFRecord) { + setState(() { + _records![index] = result; + }); + } else if (result is String && result == "Delete") { + _records!.removeAt(index); + } + } + }, + ))), + ), + ]), ) ]), ), diff --git a/example/lib/ndef_record/raw_record_setting.dart b/example/lib/ndef_record/raw_record_setting.dart index 5743b4b..b01748f 100644 --- a/example/lib/ndef_record/raw_record_setting.dart +++ b/example/lib/ndef_record/raw_record_setting.dart @@ -9,11 +9,11 @@ class NDEFRecordSetting extends StatefulWidget { : record = record ?? ndef.NDEFRecord(), super(key: key); @override - _NDEFRecordSetting createState() => _NDEFRecordSetting(); + State createState() => _NDEFRecordSetting(); } class _NDEFRecordSetting extends State { - GlobalKey _formKey = new GlobalKey(); + final GlobalKey _formKey = GlobalKey(); late TextEditingController _identifierController; late TextEditingController _payloadController; late TextEditingController _typeController; @@ -24,27 +24,21 @@ class _NDEFRecordSetting extends State { super.initState(); if (widget.record.id == null) { - _identifierController = - new TextEditingController.fromValue(TextEditingValue(text: "")); + _identifierController = TextEditingController.fromValue(TextEditingValue(text: "")); } else { - _identifierController = new TextEditingController.fromValue( - TextEditingValue(text: widget.record.id!.toHexString())); + _identifierController = TextEditingController.fromValue(TextEditingValue(text: widget.record.id!.toHexString())); } if (widget.record.payload == null) { - _payloadController = - new TextEditingController.fromValue(TextEditingValue(text: "")); + _payloadController = TextEditingController.fromValue(TextEditingValue(text: "")); } else { - _payloadController = new TextEditingController.fromValue( - TextEditingValue(text: widget.record.payload!.toHexString())); + _payloadController = + TextEditingController.fromValue(TextEditingValue(text: widget.record.payload!.toHexString())); } - if (widget.record.encodedType == null && - widget.record.decodedType == null) { + if (widget.record.encodedType == null && widget.record.decodedType == null) { // bug in ndef package (fixed in newest version) - _typeController = - new TextEditingController.fromValue(TextEditingValue(text: "")); + _typeController = TextEditingController.fromValue(TextEditingValue(text: "")); } else { - _typeController = new TextEditingController.fromValue( - TextEditingValue(text: widget.record.type!.toHexString())); + _typeController = TextEditingController.fromValue(TextEditingValue(text: widget.record.type!.toHexString())); } _dropButtonValue = ndef.TypeNameFormat.values.indexOf(widget.record.tnf); } @@ -84,10 +78,8 @@ class _NDEFRecordSetting extends State { child: Text('absoluteURI'), value: 3, ), - DropdownMenuItem( - child: Text('nfcExternal'), value: 4), - DropdownMenuItem( - child: Text('unchanged'), value: 5), + DropdownMenuItem(child: Text('nfcExternal'), value: 4), + DropdownMenuItem(child: Text('unchanged'), value: 5), DropdownMenuItem( child: Text('unknown'), value: 6, @@ -100,57 +92,44 @@ class _NDEFRecordSetting extends State { }, ), TextFormField( - decoration: - InputDecoration(labelText: 'identifier'), + decoration: InputDecoration(labelText: 'identifier'), validator: (v) { - return v!.trim().length % 2 == 0 - ? null - : 'length must be even'; + return v!.trim().length % 2 == 0 ? null : 'length must be even'; }, controller: _identifierController, ), TextFormField( decoration: InputDecoration(labelText: 'type'), validator: (v) { - return v!.trim().length % 2 == 0 - ? null - : 'length must be even'; + return v!.trim().length % 2 == 0 ? null : 'length must be even'; }, controller: _typeController, ), TextFormField( decoration: InputDecoration(labelText: 'payload'), validator: (v) { - return v!.trim().length % 2 == 0 - ? null - : 'length must be even'; + return v!.trim().length % 2 == 0 ? null : 'length must be even'; }, controller: _payloadController, ), ElevatedButton( child: Text('OK'), onPressed: () { - if ((_formKey.currentState as FormState) - .validate()) { + if ((_formKey.currentState as FormState).validate()) { Navigator.pop( context, ndef.NDEFRecord( - tnf: ndef.TypeNameFormat - .values[_dropButtonValue], - type: - (_typeController.text).toBytes(), - id: (_identifierController.text) - .toBytes(), - payload: (_payloadController.text) - .toBytes())); + tnf: ndef.TypeNameFormat.values[_dropButtonValue], + type: (_typeController.text).toBytes(), + id: (_identifierController.text).toBytes(), + payload: (_payloadController.text).toBytes())); } }, ), ElevatedButton( child: Text('Delete'), onPressed: () { - if ((_formKey.currentState as FormState) - .validate()) { + if ((_formKey.currentState as FormState).validate()) { Navigator.pop(context, 'Delete'); } }, diff --git a/example/lib/ndef_record/text_record_setting.dart b/example/lib/ndef_record/text_record_setting.dart index c893ef8..006e7b6 100644 --- a/example/lib/ndef_record/text_record_setting.dart +++ b/example/lib/ndef_record/text_record_setting.dart @@ -8,11 +8,11 @@ class NDEFTextRecordSetting extends StatefulWidget { : record = record ?? ndef.TextRecord(language: 'en', text: ''), super(key: key); @override - _NDEFTextRecordSetting createState() => _NDEFTextRecordSetting(); + State createState() => _NDEFTextRecordSetting(); } class _NDEFTextRecordSetting extends State { - GlobalKey _formKey = new GlobalKey(); + final GlobalKey _formKey = GlobalKey(); late TextEditingController _languageController; late TextEditingController _textController; late int _dropButtonValue; @@ -21,10 +21,8 @@ class _NDEFTextRecordSetting extends State { initState() { super.initState(); - _languageController = new TextEditingController.fromValue( - TextEditingValue(text: widget.record.language!)); - _textController = new TextEditingController.fromValue( - TextEditingValue(text: widget.record.text!)); + _languageController = TextEditingController.fromValue(TextEditingValue(text: widget.record.language!)); + _textController = TextEditingController.fromValue(TextEditingValue(text: widget.record.text!)); _dropButtonValue = ndef.TextEncoding.values.indexOf(widget.record.encoding); } @@ -47,10 +45,8 @@ class _NDEFTextRecordSetting extends State { DropdownButton( value: _dropButtonValue, items: [ - DropdownMenuItem( - child: Text('UTF-8'), value: 0), - DropdownMenuItem( - child: Text('UTF-16'), value: 1), + DropdownMenuItem(child: Text('UTF-8'), value: 0), + DropdownMenuItem(child: Text('UTF-16'), value: 1), ], onChanged: (value) { setState(() { @@ -59,12 +55,9 @@ class _NDEFTextRecordSetting extends State { }, ), TextFormField( - decoration: - InputDecoration(labelText: 'language'), + decoration: InputDecoration(labelText: 'language'), validator: (v) { - return v!.trim().length % 2 == 0 - ? null - : 'length must not be blank'; + return v!.trim().length % 2 == 0 ? null : 'length must not be blank'; }, controller: _languageController, ), @@ -75,13 +68,11 @@ class _NDEFTextRecordSetting extends State { ElevatedButton( child: Text('OK'), onPressed: () { - if ((_formKey.currentState as FormState) - .validate()) { + if ((_formKey.currentState as FormState).validate()) { Navigator.pop( context, ndef.TextRecord( - encoding: ndef.TextEncoding - .values[_dropButtonValue], + encoding: ndef.TextEncoding.values[_dropButtonValue], language: (_languageController.text), text: (_textController.text))); } diff --git a/example/lib/ndef_record/uri_record_setting.dart b/example/lib/ndef_record/uri_record_setting.dart index 037827c..985022e 100644 --- a/example/lib/ndef_record/uri_record_setting.dart +++ b/example/lib/ndef_record/uri_record_setting.dart @@ -8,11 +8,11 @@ class NDEFUriRecordSetting extends StatefulWidget { : record = record ?? ndef.UriRecord(prefix: '', content: ''), super(key: key); @override - _NDEFUriRecordSetting createState() => _NDEFUriRecordSetting(); + State createState() => _NDEFUriRecordSetting(); } class _NDEFUriRecordSetting extends State { - GlobalKey _formKey = new GlobalKey(); + final GlobalKey _formKey = GlobalKey(); late TextEditingController _contentController; String? _dropButtonValue; @@ -20,8 +20,7 @@ class _NDEFUriRecordSetting extends State { initState() { super.initState(); - _contentController = new TextEditingController.fromValue( - TextEditingValue(text: widget.record.content!)); + _contentController = TextEditingController.fromValue(TextEditingValue(text: widget.record.content!)); _dropButtonValue = widget.record.prefix; } @@ -44,8 +43,7 @@ class _NDEFUriRecordSetting extends State { DropdownButton( value: _dropButtonValue, items: ndef.UriRecord.prefixMap.map((value) { - return DropdownMenuItem( - child: Text(value), value: value); + return DropdownMenuItem(child: Text(value), value: value); }).toList(), onChanged: (value) { setState(() { @@ -60,8 +58,7 @@ class _NDEFUriRecordSetting extends State { ElevatedButton( child: Text('OK'), onPressed: () { - if ((_formKey.currentState as FormState) - .validate()) { + if ((_formKey.currentState as FormState).validate()) { Navigator.pop( context, ndef.UriRecord( diff --git a/example/pubspec.lock b/example/pubspec.lock index 3ec36c1..b8be7d0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.19.1" convert: dependency: transitive description: @@ -61,30 +61,30 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.9" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" flutter_nfc_kit: - dependency: "direct dev" + dependency: "direct main" description: path: ".." relative: true source: path - version: "3.4.1" + version: "3.6.2" flutter_test: dependency: "direct dev" description: flutter @@ -95,14 +95,6 @@ packages: description: flutter source: sdk version: "0.0.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" json_annotation: dependency: transitive description: @@ -111,59 +103,83 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" - logging: + leak_tracker: dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + logging: + dependency: "direct main" description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.18.0" ndef: - dependency: transitive + dependency: "direct main" description: name: ndef - sha256: "634d2b5c6f2c186e953218bac9905f3f5e1824b15e30bd1ed6e03a91cdbc7293" + sha256: "198ba3798e80cea381648569d84059dbba64cd140079fb7b0d9c3f1e0f5973f3" url: "https://pub.dev" source: hosted - version: "0.3.3" + version: "0.4.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -184,18 +200,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -216,10 +232,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.11" typed_data: dependency: transitive description: @@ -228,6 +244,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" uuid: dependency: transitive description: @@ -240,18 +264,26 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" web: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "1.1.1" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=2.0.0" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index bf7d3f5..9e6a88e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,61 +3,20 @@ description: Demonstrates how to use the flutter_nfc_kit plugin. publish_to: 'none' environment: - sdk: ">=2.12.0" + sdk: ">=3.2.0" dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.3 + flutter_nfc_kit: + path: ../ + logging: ^1.3.0 + ndef: ^0.4.0 + cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter - flutter_nfc_kit: - path: ../ - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + uses-material-design: true \ No newline at end of file diff --git a/ios/Classes/SwiftFlutterNfcKitPlugin.swift b/ios/Classes/SwiftFlutterNfcKitPlugin.swift index 56fe074..f9215bf 100644 --- a/ios/Classes/SwiftFlutterNfcKitPlugin.swift +++ b/ios/Classes/SwiftFlutterNfcKitPlugin.swift @@ -35,6 +35,7 @@ public class SwiftFlutterNfcKitPlugin: NSObject, FlutterPlugin, NFCTagReaderSess var result: FlutterResult? var tag: NFCTag? var multipleTagMessage: String? + var isActive: Bool = false public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "flutter_nfc_kit", binaryMessenger: registrar.messenger()) @@ -359,6 +360,7 @@ public class SwiftFlutterNfcKitPlugin: NSObject, FlutterPlugin, NFCTagReaderSess } else if call.method == "finish" { self.result?(FlutterError(code: "406", message: "Session not active", details: nil)) self.result = nil + self.isActive = false if let session = session { let arguments = call.arguments as! [String: Any?] @@ -375,7 +377,7 @@ public class SwiftFlutterNfcKitPlugin: NSObject, FlutterPlugin, NFCTagReaderSess } self.session = nil } - + tag = nil result(nil) } else if call.method == "setIosAlertMessage" { @@ -423,11 +425,13 @@ public class SwiftFlutterNfcKitPlugin: NSObject, FlutterPlugin, NFCTagReaderSess } // from NFCTagReaderSessionDelegate - public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) {} + public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) { + self.isActive = true + } // from NFCTagReaderSessionDelegate - public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - guard result != nil else { return; } + public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { + guard result != nil, self.session == session, self.isActive else { return; } if let nfcError = error as? NFCReaderError { NSLog("Got NFCError when reading NFC: %@", nfcError.localizedDescription) @@ -445,7 +449,7 @@ public class SwiftFlutterNfcKitPlugin: NSObject, FlutterPlugin, NFCTagReaderSess } result = nil - session = nil + self.session = nil tag = nil } diff --git a/lib/flutter_nfc_kit_web.dart b/lib/flutter_nfc_kit_web.dart deleted file mode 100644 index 170b103..0000000 --- a/lib/flutter_nfc_kit_web.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:async'; -// In order to *not* need this ignore, consider extracting the "web" version -// of your plugin as a separate package, instead of inlining it in the same -// package as the core of your plugin. -// ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html show window; -import 'dart:js_util'; -import 'package:convert/convert.dart'; - -import 'package:flutter/services.dart'; -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:flutter_nfc_kit/webusb_interop.dart'; - -/// A web implementation of the FlutterNfcKit plugin. -/// -/// Note: you should **NEVER use this class directly**, but instead use the [FlutterNfcKit] class in your project. -/// -/// You **need to implement our WebUSB protocol** on the device side for this to work. -/// See [WebUSB.md](https://github.com/nfcim/flutter_nfc_kit/blob/master/WebUSB.md) for protocol details. -class FlutterNfcKitWeb { - static void registerWith(Registrar registrar) { - final MethodChannel channel = MethodChannel( - 'flutter_nfc_kit', - const StandardMethodCodec(), - registrar, - ); - - final pluginInstance = FlutterNfcKitWeb(); - channel.setMethodCallHandler(pluginInstance.handleMethodCall); - } - - /// Handles method calls over the MethodChannel of this plugin. - /// Note: Check the "federated" architecture for a new way of doing this: - /// https://flutter.dev/go/federated-plugins - Future handleMethodCall(MethodCall call) async { - switch (call.method) { - case 'getNFCAvailability': - if (hasProperty(html.window.navigator, 'usb')) { - return 'available'; - } else { - return 'not_supported'; - } - - case 'poll': - int timeout = call.arguments["timeout"]; - bool probe = call.arguments["probeWebUSBMagic"]; - return await WebUSB.poll(timeout, probe); - - case 'transceive': - var data = call.arguments["data"]; - if (!(data is Uint8List || data is String)) { - throw PlatformException( - code: "400", - message: - "Bad argument: data should be String or Uint8List, got $data"); - } - // always pass hex string to [transceive] - var encodedData = data; - if (data is Uint8List) { - encodedData = hex.encode(data); - } - var encodedResp = await WebUSB.transceive(encodedData); - dynamic resp = encodedResp; - // return type should be the same as [data] - if (data is Uint8List) { - resp = Uint8List.fromList(hex.decode(encodedResp)); - } - return resp; - - case 'finish': - bool closeWebUSB = call.arguments["closeWebUSB"]; - return await WebUSB.finish(closeWebUSB); - - default: - throw PlatformException( - code: "501", - details: - "flutter_nfc_kit for web does not support \"${call.method}\""); - } - } -} diff --git a/lib/webusb_interop.dart b/lib/webusb_interop.dart deleted file mode 100644 index c024654..0000000 --- a/lib/webusb_interop.dart +++ /dev/null @@ -1,246 +0,0 @@ -@JS() - -/// Library that inter-ops with JavaScript on WebUSB APIs. -/// -/// Note: you should **NEVER use this library directly**, but instead use the [FlutterNfcKit] class in your project. -library webusb_interop; - -import 'dart:convert'; -import 'dart:js_util'; -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:convert/convert.dart'; -import 'package:flutter/services.dart'; -import 'package:js/js.dart'; -import 'package:logging/logging.dart'; - -final log = Logger('FlutterNFCKit:WebUSB'); - -/// The USB class code used to identify a WebUSB device that supports this protocol. -const int USB_CLASS_CODE_VENDOR_SPECIFIC = 0xFF; - -@JS('navigator.usb') -class _USB { - external static dynamic requestDevice(_USBDeviceRequestOptions options); - // ignore: unused_field - external static Function ondisconnect; -} - -@JS() -@anonymous -class _USBDeviceRequestOptions { - external factory _USBDeviceRequestOptions({List<_USBDeviceFilter> filters}); -} - -@JS() -@anonymous -class _USBDeviceFilter { - external factory _USBDeviceFilter({int classCode}); -} - -@JS() -@anonymous -class _USBControlTransferParameters { - external factory _USBControlTransferParameters( - {String requestType, - String recipient, - int request, - int value, - int index}); -} - -/// Wraps around WebUSB APIs from browsers to provide low-level interfaces such as [poll] for [FlutterNfcKitWeb]. -/// -/// Note: you should **NEVER use this class directly**, but instead use the [FlutterNfcKit] class in your project. -class WebUSB { - static dynamic _device; - static String customProbeData = ""; - - static bool _deviceAvailable() { - return _device != null && getProperty(_device, 'opened'); - } - - static void _onDisconnect(event) { - _device = null; - log.info('device is disconnected from WebUSB API'); - } - - static const USB_PROBE_MAGIC = '_NFC_IM_'; - - /// Try to poll a WebUSB device according to our protocol. - static Future poll(int timeout, bool probeMagic) async { - // request WebUSB device with custom classcode - if (!_deviceAvailable()) { - var devicePromise = _USB.requestDevice(_USBDeviceRequestOptions(filters: [ - _USBDeviceFilter(classCode: USB_CLASS_CODE_VENDOR_SPECIFIC) - ])); - dynamic device = await promiseToFuture(devicePromise); - try { - await promiseToFuture(callMethod(device, 'open', List.empty())) - .then((_) => - promiseToFuture(callMethod(device, 'claimInterface', [1]))) - .timeout(Duration(milliseconds: timeout)); - _device = device; - _USB.ondisconnect = allowInterop(_onDisconnect); - log.info("WebUSB device opened", _device); - } on TimeoutException catch (_) { - log.severe("Polling tag timeout"); - throw PlatformException(code: "408", message: "Polling tag timeout"); - } on Exception catch (e) { - log.severe("Poll error", e); - throw PlatformException( - code: "500", message: "WebUSB API error", details: e); - } - - if (probeMagic) { - try { - // PROBE request - var promise = callMethod(_device, 'controlTransferIn', [ - _USBControlTransferParameters( - requestType: 'vendor', - recipient: 'interface', - request: 0xff, - value: 0, - index: 1), - 1 - ]); - var resp = await promiseToFuture(promise); - if (getProperty(resp, 'status') == 'stalled') { - throw PlatformException( - code: "500", message: "Device error: transfer stalled"); - } - var result = - (getProperty(resp, 'data').buffer as ByteBuffer).asUint8List(); - if (result.length < USB_PROBE_MAGIC.length || - result.sublist(0, USB_PROBE_MAGIC.length) != - Uint8List.fromList(USB_PROBE_MAGIC.codeUnits)) { - throw PlatformException( - code: "500", - message: - "Device error: invalid probe response: ${hex.encode(result)}, should begin with $USB_PROBE_MAGIC"); - } - customProbeData = hex.encode(result.sublist(USB_PROBE_MAGIC.length)); - } on Exception catch (e) { - log.severe("Probe error", e); - throw PlatformException( - code: "500", message: "WebUSB API error", details: e); - } - } else { - customProbeData = ""; - } - } - // get VID & PID - int vendorId = getProperty(_device, 'vendorId'); - int productId = getProperty(_device, 'productId'); - String id = - '${vendorId.toRadixString(16).padLeft(4, '0')}:${productId.toRadixString(16).padLeft(4, '0')}'; - return json.encode({ - 'type': 'webusb', - 'id': id, - 'standard': 'nfc-im-webusb-protocol', - 'customProbeData': customProbeData - }); - } - - static Future _doTransceive(Uint8List capdu) async { - // send a command (CMD) - var promise = callMethod(_device, 'controlTransferOut', [ - _USBControlTransferParameters( - requestType: 'vendor', - recipient: 'interface', - request: 0, - value: 0, - index: 1), - capdu - ]); - await promiseToFuture(promise); - // wait for execution to finish (STAT) - while (true) { - promise = callMethod(_device, 'controlTransferIn', [ - _USBControlTransferParameters( - requestType: 'vendor', - recipient: 'interface', - request: 2, - value: 0, - index: 1), - 1 - ]); - var resp = await promiseToFuture(promise); - if (getProperty(resp, 'status') == 'stalled') { - throw PlatformException( - code: "500", message: "Device error: transfer stalled"); - } - var code = getProperty(resp, 'data').buffer.asUint8List()[0]; - if (code == 0) { - break; - } else if (code == 1) { - await Future.delayed(const Duration(microseconds: 100)); - } else { - throw PlatformException( - code: "500", message: "Device error: unexpected RESP code $code"); - } - } - // get the response (RESP) - promise = callMethod(_device, 'controlTransferIn', [ - _USBControlTransferParameters( - requestType: 'vendor', - recipient: 'interface', - request: 1, - value: 0, - index: 1), - 1500 - ]); - var resp = await promiseToFuture(promise); - var deviceStatus = getProperty(resp, 'status'); - if (deviceStatus != 'ok') { - throw PlatformException( - code: "500", - message: - "Device error: status should be \"ok\", got \"$deviceStatus\""); - } - return getProperty(resp, 'data').buffer.asUint8List(); - } - - /// Transceive data with polled WebUSB device according to our protocol. - static Future transceive(String capdu) async { - log.config('CAPDU: $capdu'); - if (!_deviceAvailable()) { - throw PlatformException( - code: "406", message: "No tag polled or device already disconnected"); - } - try { - var rawCAPDU = Uint8List.fromList(hex.decode(capdu)); - var rawRAPDU = await _doTransceive(rawCAPDU); - String rapdu = hex.encode(rawRAPDU); - log.config('RAPDU: $rapdu'); - return rapdu; - } on TimeoutException catch (_) { - log.severe("Transceive timeout"); - throw PlatformException(code: "408", message: "Transceive timeout"); - } on PlatformException catch (e) { - log.severe("Transceive error", e); - rethrow; - } on Exception catch (e) { - log.severe("Transceive error", e); - throw PlatformException( - code: "500", message: "WebUSB API error", details: e); - } - } - - /// Finish this session, also end WebUSB session if explicitly asked by user. - static Future finish(bool closeWebUSB) async { - if (_deviceAvailable()) { - if (closeWebUSB) { - try { - await promiseToFuture(callMethod(_device, "close", List.empty())); - } on Exception catch (e) { - log.severe("Finish error: ", e); - throw PlatformException( - code: "500", message: "WebUSB API error", details: e); - } - _device = null; - } - } - } -} diff --git a/pubspec.yaml b/pubspec.yaml index b5feb02..1a55407 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: flutter_nfc_kit description: Provide NFC functionality on Android, iOS & Web, including reading metadata, read & write NDEF records, and transceive layer 3 & 4 data with NFC tags / cards -version: 3.4.1 +version: 3.6.2 homepage: "https://github.com/nfcim/flutter_nfc_kit" environment: - sdk: ">=2.12.0 <4.0.0" - flutter: ">=2.0.0" + sdk: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" dependencies: flutter: @@ -13,16 +13,17 @@ dependencies: flutter_web_plugins: sdk: flutter json_annotation: ^4.8.1 - ndef: ^0.3.3 + ndef: ^0.4.0 convert: ^3.1.1 logging: ^1.2.0 - js: ^0.6.7 + web: ^1.1.1 + universal_platform: ^1.1.0 dev_dependencies: flutter_test: sdk: flutter - lints: ^3.0.0 - build_runner: ^2.4.6 + lints: ^6.0.0 + build_runner: ^2.4.9 json_serializable: ^6.7.1 flutter: @@ -33,6 +34,3 @@ flutter: pluginClass: FlutterNfcKitPlugin ios: pluginClass: FlutterNfcKitPlugin - web: - pluginClass: FlutterNfcKitWeb - fileName: flutter_nfc_kit_web.dart