diff --git a/.gitignore b/.gitignore index 51adbce..55cd06b 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,6 @@ ios/Package.resolved # local-agent-setup: personal/machine-local agent overrides (gitignored) AGENTS.override.md + +# SwiftPM/SourceKit build output for the plugin package itself. +ios/locus/.build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 715e49a..b3a3073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. ### Fixed - **Android: activity recognition can deliver results on Android 12+ (#61)** — The explicitly targeted activity PendingIntent is mutable so Google Play services can attach activity result extras. +- **Permissions: preserve the settings fallback after permanent denial** — Use the permission request result when deciding whether to offer settings, as updated Android permission status queries no longer report permanent denial. + +### Changed + +- Raise the `permission_handler` patch minimum to 13.0.2 and refresh its example resolution; update the example's AndroidX core overrides to 1.19.1. +- The example app now uses `material_ui` and `google_fonts` 9 and requires Flutter 3.47 or newer; its Android build uses Gradle 9.8.2, the Android Gradle Plugin 9.5.0 and Kotlin 2.4.21. The plugin's own requirements are unchanged. ## [2.4.1] - 2026-08-23 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 839a1e8..a3fc243 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ By participating, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md). ## Development Environment -1. Ensure you have the Flutter SDK (stable channel) installed. +1. Ensure you have the Flutter SDK (stable channel, 3.47 or newer for the example app) installed. 2. Clone the repository and fetch dependencies: ```bash flutter pub get diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 41581dd..7bfa548 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -65,7 +65,7 @@ flutter { configurations.all { resolutionStrategy { force("androidx.browser:browser:1.10.0") - force("androidx.core:core:1.19.0") - force("androidx.core:core-ktx:1.19.0") + force("androidx.core:core:1.19.1") + force("androidx.core:core-ktx:1.19.1") } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar index 13372ae..9fa7f28 100644 Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and b/example/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 5803cbd..988da1a 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,9 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-all.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip diff --git a/example/android/gradlew b/example/android/gradlew index 9d82f78..249efbb 100755 --- a/example/android/gradlew +++ b/example/android/gradlew @@ -1,74 +1,128 @@ -#!/usr/bin/env bash +#!/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 UN*X -## +# +# gradlew 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 gradlew +# +# 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/3d91ce3b8caaf77ad09f381f43615b715b53f72c/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\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +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 +131,118 @@ 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 +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 -# 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 +# 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" ) -# 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\"" + 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 - i=$((i+1)) + # 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 - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# 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/example/android/gradlew.bat b/example/android/gradlew.bat index 8a0b282..3185a43 100644 --- a/example/android/gradlew.bat +++ b/example/android/gradlew.bat @@ -1,90 +1,112 @@ -@if "%DEBUG%" == "" @echo off +@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 gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +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. +1>&2 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. -goto fail +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants +if exist "%JAVA_EXE%" goto execute -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args +1>&2 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. -: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=%$ +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel -:omega +@rem This label must not be changed. We rely on old scripts being able to jump to this point. +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts index 3353a1e..0549aed 100644 --- a/example/android/settings.gradle.kts +++ b/example/android/settings.gradle.kts @@ -18,8 +18,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.2" - id("com.android.application") version "9.3.1" apply false - id("org.jetbrains.kotlin.android") version "2.4.10" apply false + id("com.android.application") version "9.5.0" apply false + id("org.jetbrains.kotlin.android") version "2.4.21" apply false } include(":app") diff --git a/example/lib/app.dart b/example/lib/app.dart index 75f654f..9487a3e 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:locus_example/demos/demos_screen.dart'; diff --git a/example/lib/demos/battery_demo.dart b/example/lib/demos/battery_demo.dart index 07de357..95dc9b0 100644 --- a/example/lib/demos/battery_demo.dart +++ b/example/lib/demos/battery_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/info_row.dart'; diff --git a/example/lib/demos/demos_screen.dart b/example/lib/demos/demos_screen.dart index af3fffe..7ff0364 100644 --- a/example/lib/demos/demos_screen.dart +++ b/example/lib/demos/demos_screen.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/battery_demo.dart'; import 'package:locus_example/demos/diagnostics_demo.dart'; diff --git a/example/lib/demos/diagnostics_demo.dart b/example/lib/demos/diagnostics_demo.dart index 1175601..3a66a89 100644 --- a/example/lib/demos/diagnostics_demo.dart +++ b/example/lib/demos/diagnostics_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/info_row.dart'; @@ -111,9 +111,9 @@ class StoredLocationsCard extends StatelessWidget { return ListTile( leading: CircleAvatar( radius: 16, - backgroundColor: Theme.of( - context, - ).colorScheme.primaryContainer, + backgroundColor: Theme.of(context) + .colorScheme + .primaryContainer, child: Text( '${i + 1}', style: TextStyle( diff --git a/example/lib/demos/geofencing_demo.dart b/example/lib/demos/geofencing_demo.dart index ac40ff1..f2ebb45 100644 --- a/example/lib/demos/geofencing_demo.dart +++ b/example/lib/demos/geofencing_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/section_header.dart'; diff --git a/example/lib/demos/privacy_demo.dart b/example/lib/demos/privacy_demo.dart index 20f3801..dd7d5ca 100644 --- a/example/lib/demos/privacy_demo.dart +++ b/example/lib/demos/privacy_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/section_header.dart'; diff --git a/example/lib/demos/sync_demo.dart b/example/lib/demos/sync_demo.dart index eb1b178..3d7b92f 100644 --- a/example/lib/demos/sync_demo.dart +++ b/example/lib/demos/sync_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/info_row.dart'; diff --git a/example/lib/demos/tracking_demo.dart b/example/lib/demos/tracking_demo.dart index f71e437..f4b5120 100644 --- a/example/lib/demos/tracking_demo.dart +++ b/example/lib/demos/tracking_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus/locus.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/info_row.dart'; diff --git a/example/lib/demos/trips_demo.dart b/example/lib/demos/trips_demo.dart index 3b6f08e..c67256e 100644 --- a/example/lib/demos/trips_demo.dart +++ b/example/lib/demos/trips_demo.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus_example/demos/widgets/action_button.dart'; import 'package:locus_example/demos/widgets/section_header.dart'; diff --git a/example/lib/demos/widgets/action_button.dart b/example/lib/demos/widgets/action_button.dart index e8791d2..92dbd4f 100644 --- a/example/lib/demos/widgets/action_button.dart +++ b/example/lib/demos/widgets/action_button.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// Standard action button used across the demo screens. Renders either a /// filled or outlined button depending on [filled], and disables itself diff --git a/example/lib/demos/widgets/info_row.dart b/example/lib/demos/widgets/info_row.dart index 8a90a4b..3821846 100644 --- a/example/lib/demos/widgets/info_row.dart +++ b/example/lib/demos/widgets/info_row.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// A label/value row prefixed by an icon, used inside cards to surface /// SDK state at a glance. diff --git a/example/lib/demos/widgets/profile_chip.dart b/example/lib/demos/widgets/profile_chip.dart index 445400b..1dc0518 100644 --- a/example/lib/demos/widgets/profile_chip.dart +++ b/example/lib/demos/widgets/profile_chip.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// Tap-target tile rendered inside a horizontal row of tracking-profile /// options. Wraps itself in [Expanded] so it consumes equal width inside diff --git a/example/lib/demos/widgets/quick_action_tile.dart b/example/lib/demos/widgets/quick_action_tile.dart index f1b7ac2..e895a39 100644 --- a/example/lib/demos/widgets/quick_action_tile.dart +++ b/example/lib/demos/widgets/quick_action_tile.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// Square tap target used inside the dashboard's quick-action grid. class QuickActionTile extends StatelessWidget { diff --git a/example/lib/demos/widgets/section_header.dart b/example/lib/demos/widgets/section_header.dart index 5bae92e..93fa0e1 100644 --- a/example/lib/demos/widgets/section_header.dart +++ b/example/lib/demos/widgets/section_header.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// Header row used at the top of each demo card. Renders an icon, a title, /// and an optional trailing widget (typically a chip or count). diff --git a/example/lib/demos/widgets/status_indicator.dart b/example/lib/demos/widgets/status_indicator.dart index 7bcfa67..7405e48 100644 --- a/example/lib/demos/widgets/status_indicator.dart +++ b/example/lib/demos/widgets/status_indicator.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// Pill-shaped indicator showing whether something is active, with a label. class StatusIndicator extends StatelessWidget { diff --git a/example/lib/harness/event_log_screen.dart b/example/lib/harness/event_log_screen.dart index 0b40322..a1839e2 100644 --- a/example/lib/harness/event_log_screen.dart +++ b/example/lib/harness/event_log_screen.dart @@ -9,7 +9,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus_example/harness/event_log_exporter.dart'; import 'package:locus_example/harness/event_recorder.dart'; import 'package:locus_example/harness/recorded_event.dart'; diff --git a/example/lib/main.dart b/example/lib/main.dart index d852cc8..713190a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus_example/app.dart'; export 'package:locus_example/app.dart' show LocusExampleApp; diff --git a/example/lib/mock_backend/mock_backend_impl.dart b/example/lib/mock_backend/mock_backend_impl.dart index 03eba61..0cba490 100644 --- a/example/lib/mock_backend/mock_backend_impl.dart +++ b/example/lib/mock_backend/mock_backend_impl.dart @@ -31,11 +31,9 @@ class HttpMockBackend implements MockBackend { HttpMockBackend._( this._server, { required MockMode initialMode, - required Duration slowResponseDelay, - required int outageRequestCount, - }) : _mode = initialMode, - _slowResponseDelay = slowResponseDelay, - _outageRequestCount = outageRequestCount { + required this._slowResponseDelay, + required this._outageRequestCount, + }) : _mode = initialMode { _subscription = _server.listen( _handle, onError: (Object _, StackTrace _) { diff --git a/example/lib/mock_backend/mock_backend_screen.dart b/example/lib/mock_backend/mock_backend_screen.dart index d548367..3e6d451 100644 --- a/example/lib/mock_backend/mock_backend_screen.dart +++ b/example/lib/mock_backend/mock_backend_screen.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:flutter/services.dart'; import 'package:locus_example/mock_backend/mock_backend.dart'; diff --git a/example/lib/scenarios/scenario_detail_screen.dart b/example/lib/scenarios/scenario_detail_screen.dart index f2455aa..04c071d 100644 --- a/example/lib/scenarios/scenario_detail_screen.dart +++ b/example/lib/scenarios/scenario_detail_screen.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus_example/harness/event_recorder.dart'; import 'package:locus_example/harness/recorded_event.dart'; import 'package:locus_example/mock_backend/mock_backend.dart'; diff --git a/example/lib/scenarios/scenario_screen.dart b/example/lib/scenarios/scenario_screen.dart index eda2fe5..22c8f7f 100644 --- a/example/lib/scenarios/scenario_screen.dart +++ b/example/lib/scenarios/scenario_screen.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:locus_example/harness/event_recorder.dart'; import 'package:locus_example/mock_backend/mock_backend.dart'; import 'package:locus_example/scenarios/scenario.dart'; diff --git a/example/pubspec.lock b/example/pubspec.lock index d77e006..edd8d9f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" code_assets: dependency: transitive description: name: code_assets - sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + sha256: "828110d598123b5ea96c00c9f3c72105bf79f8ee36c20a39b26209ade421ec57" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" collection: dependency: transitive description: @@ -61,18 +61,26 @@ packages: dependency: transitive description: name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + sha256: e3626ae6270383c9e3a549f34c9e4cd26c09ae6a9adaab2bf4737a00340efdc4 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.0.8" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + sha256: b7cc2390aac296c083bea762859413eb1cf5b630c70e0c7e4d831018f762a8b2 url: "https://pub.dev" source: hosted - version: "1.0.9" + version: "2.0.0" + cupertino_ui: + dependency: transitive + description: + name: cupertino_ui + sha256: "369e39a1c5c030a6212589c3da1809920b1c2cfade7b02aade7695bfa4b88c3a" + url: "https://pub.dev" + source: hosted + version: "1.1.6" fake_async: dependency: transitive description: @@ -107,14 +115,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + sha256: "100c19dcc4ff60083e95a44a6f44f3a1bb887d353c5d86f6a9b688aa3cb46236" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" flutter_test: dependency: "direct dev" description: flutter @@ -134,18 +147,18 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: e3cb3ee6b47fd2472c23de6da5744796a4da195137759ddb3fbcc9467b7b3c7d + sha256: fdbbfbbb1a62f376ab29fa9627561d389751fb984da0b630ce3326e272911c8a url: "https://pub.dev" source: hosted - version: "8.2.1" + version: "9.0.0" hooks: dependency: transitive description: name: hooks - sha256: "03a564c3704524ee0f7fc56fc621e8796cc2eb8c24d1f1a33b34979815b285c4" + sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" http: dependency: transitive description: @@ -167,22 +180,30 @@ packages: description: flutter source: sdk version: "0.0.0" + intl: + dependency: transitive + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" jni: dependency: transitive description: name: jni - sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + sha256: "2e91e6087291affd10b31b6c872f3e3ac7dc5d93b51b5ba485d930c8f08bde6c" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.1.2" jni_flutter: dependency: transitive description: name: jni_flutter - sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" + sha256: "0dd33d67797560f66e961a6053882d4df54bd345bf063b8fe7ea133f7ad8b191" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4+1" jni_util: dependency: transitive description: @@ -246,6 +267,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + material_ui: + dependency: "direct main" + description: + name: material_ui + sha256: a30f5d0625b6b83770f53ca3f7aafd6fd9d147cb3853f54a70668541046fe5d6 + url: "https://pub.dev" + source: hosted + version: "1.10.0" meta: dependency: transitive description: @@ -258,10 +287,10 @@ packages: dependency: transitive description: name: objective_c - sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + sha256: "5c80c2ad6e2c0397de5db38c6d653e69616a3397e13aa7633bf39eb668f2041b" url: "https://pub.dev" source: hosted - version: "9.5.0" + version: "9.6.2" package_config: dependency: transitive description: @@ -338,26 +367,26 @@ packages: dependency: transitive description: name: permission_handler - sha256: e7317eb2eb611d1bf0386b6b974be9c50449f975f19a90a7b8ea013550302b30 + sha256: e6bcecadfe40df6b888141f23cbaf6ba6c3da49fb33f6ff887d060dd48544fbd url: "https://pub.dev" source: hosted - version: "13.0.1" + version: "13.0.2" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d7676c6fcf2f0b92537ec41476a6ead45a00b0d8bbb852395a6f9f33f49d6242 + sha256: baf93fbf80008c240b2cf5e6d62e9495ea7727fe327444d135c44bfff9af6b6e url: "https://pub.dev" source: hosted - version: "14.0.0" + version: "14.1.1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 + sha256: "82d31c9b321c45785ccd73bce9c63af17037116193b4000b998a4363059adb6e" url: "https://pub.dev" source: hosted - version: "9.6.1" + version: "9.6.5" permission_handler_html: dependency: transitive description: @@ -370,34 +399,34 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 + sha256: ed86a61c190258fdd65de395ea0632822e3415c1faec38eae0c31b479c28a531 url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.4.1" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd + sha256: f411872409a099a7f4826f80cb98cb903317be890cbf8a2e6d062024cd88e4fb url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.3" petitparser: dependency: transitive description: name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + sha256: "2f1564aadb14215e478563b5406cb80729d022158c01dc50c96013c2f7eb4790" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.1.0" platform: dependency: transitive description: name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec url: "https://pub.dev" source: hosted - version: "3.1.6" + version: "3.2.0" plugin_platform_interface: dependency: transitive description: @@ -410,26 +439,26 @@ packages: dependency: transitive description: name: process - sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891" url: "https://pub.dev" source: hosted - version: "5.0.5" + version: "5.0.6" pub_semver: dependency: transitive description: name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" record_use: dependency: transitive description: name: record_use - sha256: "3b4ab682aff40175afca6ff090cc5aa7ce9dafe8dfbae1537a6c678e6678baee" + sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" sky_engine: dependency: transitive description: flutter @@ -447,10 +476,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" url: "https://pub.dev" source: hosted - version: "1.12.1" + version: "1.12.2" stream_channel: dependency: transitive description: @@ -503,10 +532,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" + sha256: "82885f6ac633044e7a4e4d8d745724a67aa0dc50af5d8a81ec29379f63c29035" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.6" vector_graphics_codec: dependency: transitive description: @@ -519,42 +548,42 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" + sha256: "4c9d2e7121eea8b4f160e6e435900a0ae7214cfc97c2d6cf84c25532b789b50d" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.4" vector_math: dependency: transitive description: name: vector_math - sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 + sha256: fa49e1674cb84acb900141714e6c6e1f9ae099827c24d141430ff6819db4e1bc url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" vm_service: dependency: transitive description: name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + sha256: "89b11d62e1040ee681af3b4f1c97a803ecbf010c6e8b9c5896ee5b08c3bf7b78" url: "https://pub.dev" source: hosted - version: "15.2.0" + version: "15.4.0" web: dependency: transitive description: name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + sha256: e119e82ac4795c7925103b7cf3768191f33941b99e7ccffbd6c645201d9f606b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" webdriver: dependency: transitive description: name: webdriver - sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + sha256: "28b82ec894fed45dd71c23ba62d1af973ed97dd59a4f5790a4d38b0b13e5657e" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" xdg_directories: dependency: transitive description: @@ -575,10 +604,10 @@ packages: dependency: transitive description: name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.13.0 <4.0.0" + flutter: ">=3.47.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 4f7ec96..ff92027 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,20 +5,21 @@ description: Demonstrates how to use the Locus background geolocation plugin. publish_to: "none" environment: - sdk: ">=3.10.0 <4.0.0" - flutter: ">=3.38.0" + sdk: ">=3.13.0 <4.0.0" + flutter: ">=3.47.0" dependencies: flutter: sdk: flutter - google_fonts: ^8.2.1 - flutter_svg: ^2.3.0 + google_fonts: ^9.0.0 + flutter_svg: ^2.3.2 + material_ui: ^1.10.0 locus: path: ../ - cupertino_icons: ^1.0.9 + cupertino_icons: ^2.0.0 dev_dependencies: flutter_test: diff --git a/lib/src/core/permission_assistant.dart b/lib/src/core/permission_assistant.dart index 44b12ce..a790455 100644 --- a/lib/src/core/permission_assistant.dart +++ b/lib/src/core/permission_assistant.dart @@ -34,8 +34,7 @@ class PermissionAssistant { final always = await PermissionService.requestAlways(); if (!always.isGranted) { - if (delegate?.onOpenSettings != null && - await Permission.locationAlways.isPermanentlyDenied) { + if (delegate?.onOpenSettings != null && always.isPermanentlyDenied) { await delegate!.onOpenSettings!(); } return false; diff --git a/pubspec.yaml b/pubspec.yaml index 0a6a5a6..64fd675 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ environment: dependencies: flutter: sdk: flutter - permission_handler: ^13.0.1 + permission_handler: ^13.0.2 logging: ^1.3.0 # CLI dependency. Consumed only by `bin/*.dart` (locus / setup / doctor / # migrate executables). Pub's `executables:` contract requires CLI deps diff --git a/test/unit/core/permission_assistant_test.dart b/test/unit/core/permission_assistant_test.dart new file mode 100644 index 0000000..e8cf551 --- /dev/null +++ b/test/unit/core/permission_assistant_test.dart @@ -0,0 +1,130 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:locus/src/core/permission_assistant.dart'; +import 'package:permission_handler/permission_handler.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + const channel = MethodChannel('flutter.baseflow.com/permissions/methods'); + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + final calls = []; + var foregroundStatus = PermissionStatus.granted; + var backgroundStatus = PermissionStatus.permanentlyDenied; + + setUp(() { + calls.clear(); + foregroundStatus = PermissionStatus.granted; + backgroundStatus = PermissionStatus.permanentlyDenied; + messenger.setMockMethodCallHandler(channel, (MethodCall call) async { + calls.add(call); + if (call.method == 'checkPermissionStatus') { + // A fresh request result is authoritative over a stale status query. + return PermissionStatus.denied.index; + } + if (call.method != 'requestPermissions') { + throw StateError('Unexpected method: ${call.method}'); + } + final Object? arguments = call.arguments; + if (arguments is! List || arguments.length != 1) { + throw StateError('Expected one permission per request'); + } + final permission = arguments.single; + if (permission == Permission.locationWhenInUse.value) { + return { + Permission.locationWhenInUse.value: foregroundStatus.index, + }; + } + if (permission == Permission.locationAlways.value) { + return { + Permission.locationAlways.value: backgroundStatus.index, + }; + } + throw StateError('Unexpected permission: $permission'); + }); + }); + tearDown(() => messenger.setMockMethodCallHandler(channel, null)); + + test( + 'permanent background denial opens settings from the request result', + () async { + var settingsOpened = 0; + final granted = await PermissionAssistant.requestBackgroundWorkflow( + delegate: PermissionFlowDelegate( + onOpenSettings: () async { + settingsOpened++; + }, + ), + ); + expect(granted, isFalse); + expect(settingsOpened, 1); + expect(calls.map((call) => call.method), [ + 'requestPermissions', + 'requestPermissions', + ]); + expect(calls.map((call) => call.arguments), [ + [Permission.locationWhenInUse.value], + [Permission.locationAlways.value], + ]); + }, + ); + + test( + 'ordinary background denial does not open settings or recheck status', + () async { + backgroundStatus = PermissionStatus.denied; + var settingsOpened = false; + expect( + await PermissionAssistant.requestBackgroundWorkflow( + delegate: PermissionFlowDelegate( + onOpenSettings: () async { + settingsOpened = true; + }, + ), + ), + isFalse, + ); + expect(settingsOpened, isFalse); + expect(calls.map((call) => call.method), [ + 'requestPermissions', + 'requestPermissions', + ]); + }, + ); + + test( + 'foreground denial stops before requesting background permission', + () async { + foregroundStatus = PermissionStatus.denied; + var settingsOpened = false; + expect( + await PermissionAssistant.requestBackgroundWorkflow( + delegate: PermissionFlowDelegate( + onOpenSettings: () async { + settingsOpened = true; + }, + ), + ), + isFalse, + ); + expect(settingsOpened, isFalse); + expect(calls.map((call) => call.arguments), [ + [Permission.locationWhenInUse.value], + ]); + }, + ); + + test('settings callback failures propagate to the caller', () async { + final failure = StateError('settings delegate failed'); + await expectLater( + PermissionAssistant.requestBackgroundWorkflow( + delegate: PermissionFlowDelegate( + onOpenSettings: () async { + throw failure; + }, + ), + ), + throwsA(same(failure)), + ); + }); +}