diff --git a/android/build.gradle b/android/build.gradle index 6d487f8..1ede7a3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,55 +1,39 @@ group 'ca.couver.privacy_screen' version '1.0-SNAPSHOT' -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - } -} - +// AGP and Kotlin plugins are provided by the consuming app's root classpath. +// This is the standard Flutter plugin pattern — no buildscript block needed. apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - // conditional for compatibility with older gradle versions - if (project.android.hasProperty('namespace')) { - namespace 'ca.couver.privacy_screen' + namespace 'ca.couver.privacy_screen' + compileSdkVersion 36 + + defaultConfig { + minSdkVersion 21 } - compileSdkVersion 31 compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } +} - defaultConfig { - minSdkVersion 16 - } +repositories { + google() + mavenCentral() } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.lifecycle:lifecycle-process:2.5.1' + // kotlin-stdlib is added automatically by the Kotlin Gradle plugin. + implementation 'androidx.lifecycle:lifecycle-process:2.8.4' } diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..980502d Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e2847c8 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.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..faf9300 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# 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 + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + 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" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..9b42019 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,94 @@ +@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 + +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 %* + +: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/src/main/kotlin/ca/couver/privacy_screen/PrivacyScreenPlugin.kt b/android/src/main/kotlin/ca/couver/privacy_screen/PrivacyScreenPlugin.kt index 1d0904d..4686e8a 100644 --- a/android/src/main/kotlin/ca/couver/privacy_screen/PrivacyScreenPlugin.kt +++ b/android/src/main/kotlin/ca/couver/privacy_screen/PrivacyScreenPlugin.kt @@ -4,7 +4,9 @@ import android.app.Activity import android.content.Context import android.view.WindowManager.LayoutParams import androidx.annotation.NonNull -import androidx.lifecycle.* +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -19,46 +21,58 @@ class PrivacyScreenPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Def private lateinit var channel: MethodChannel private var activity: Activity? = null private lateinit var context: Context - private var autoLockAfterSeconds: Number = -1 + + // Plugin configuration + private var enableSecure: Boolean = false + private var autoLockAfterSeconds: Long = -1 private var timeEnteredBackground: Long = 0 - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "channel.couver.privacy_screen") + // -------------------- FlutterPlugin -------------------- + + override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + context = binding.applicationContext + channel = MethodChannel(binding.binaryMessenger, "channel.couver.privacy_screen") channel.setMethodCallHandler(this) - this.context = flutterPluginBinding.applicationContext ProcessLifecycleOwner.get().lifecycle.addObserver(this) } + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + ProcessLifecycleOwner.get().lifecycle.removeObserver(this) + channel.setMethodCallHandler(null) + } + + // -------------------- MethodCallHandler -------------------- + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { when (call.method) { "updateConfig" -> { - activity?.window?.let { window -> - if (call.argument("enableSecureAndroid") == true) { - window.addFlags(LayoutParams.FLAG_SECURE) - } else { - window.clearFlags(LayoutParams.FLAG_SECURE) - } - } - autoLockAfterSeconds = call.argument("autoLockAfterSecondsAndroid") ?: -1 + enableSecure = call.argument("enableSecureAndroid") ?: false + autoLockAfterSeconds = + call.argument("autoLockAfterSecondsAndroid")?.toLong() ?: -1 + + applySecureFlag() // Apply immediately if activity is available result.success(true) } - else -> { - result.notImplemented() - } + else -> result.notImplemented() } } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - ProcessLifecycleOwner.get().lifecycle.removeObserver(this) + private fun applySecureFlag() { + activity?.window?.let { window -> + if (enableSecure) { + window.addFlags(LayoutParams.FLAG_SECURE) + } else { + window.clearFlags(LayoutParams.FLAG_SECURE) + } + } } - // DefaultLifecycleObserver + // -------------------- DefaultLifecycleObserver -------------------- private fun judgeLock() { - if (autoLockAfterSeconds.toLong() >= 0 && + if (autoLockAfterSeconds >= 0 && timeEnteredBackground > 0 && - (System.currentTimeMillis() - timeEnteredBackground) / 1000 > autoLockAfterSeconds.toLong() + (System.currentTimeMillis() - timeEnteredBackground) / 1000 > autoLockAfterSeconds ) { channel.invokeMethod("lock", null) } @@ -68,43 +82,38 @@ class PrivacyScreenPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Def override fun onResume(owner: LifecycleOwner) { channel.invokeMethod("onLifeCycle", "onResume") judgeLock() - super.onResume(owner) - } - - override fun onDestroy(owner: LifecycleOwner) { - channel.invokeMethod("onLifeCycle", "onDestroy") - super.onDestroy(owner) } override fun onPause(owner: LifecycleOwner) { channel.invokeMethod("onLifeCycle", "onPause") timeEnteredBackground = System.currentTimeMillis() - super.onPause(owner) - } - - override fun onStop(owner: LifecycleOwner) { - channel.invokeMethod("onLifeCycle", "onStop") - super.onStop(owner) } override fun onStart(owner: LifecycleOwner) { channel.invokeMethod("onLifeCycle", "onStart") - super.onStart(owner) + } + + override fun onStop(owner: LifecycleOwner) { + channel.invokeMethod("onLifeCycle", "onStop") } override fun onCreate(owner: LifecycleOwner) { channel.invokeMethod("onLifeCycle", "onCreate") - super.onCreate(owner) } - // ActivityAware + override fun onDestroy(owner: LifecycleOwner) { + channel.invokeMethod("onLifeCycle", "onDestroy") + } + + // -------------------- ActivityAware -------------------- override fun onAttachedToActivity(binding: ActivityPluginBinding) { - this.activity = binding.activity + activity = binding.activity + applySecureFlag() // Apply FLAG_SECURE as soon as activity is available } override fun onDetachedFromActivityForConfigChanges() { - this.activity = null + activity = null } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { @@ -112,6 +121,6 @@ class PrivacyScreenPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Def } override fun onDetachedFromActivity() { - this.activity = null + activity = null } -} \ No newline at end of file +} diff --git a/example/.gitignore b/example/.gitignore index a8e938c..221b422 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f..391a902 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 9.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 1e8c3c9..e72e0b4 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 55c983f..9f7ffab 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,28 +1,22 @@ PODS: - Flutter (1.0.0) - - privacy_screen (0.0.1): - - Flutter - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - - privacy_screen (from `.symlinks/plugins/privacy_screen/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter - privacy_screen: - :path: ".symlinks/plugins/privacy_screen/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e - url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + url_launcher_ios: 2d16952b3a69ce3f0b2c7b12d38ba18ff5177fc0 -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: 0dbd5a87e0ace00c9610d2037ac22083a01f861d -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index acf3c0a..f3b7901 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -14,6 +14,7 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; D38B9B2992C271FF93B23D92 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0095C0B2284FC252EFA5882 /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -47,6 +48,9 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B0095C0B2284FC252EFA5882 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E523EB41CC4ADCCA8723AB96 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; }; + 78DABEA22ED26510000E7860 /* privacy_screen */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = privacy_screen; path = ../../ios/privacy_screen; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,6 +58,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, D38B9B2992C271FF93B23D92 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -72,6 +77,9 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78DABEA22ED26510000E7860 /* privacy_screen */, + 784666492D4C4C64000A1A5F /* FlutterFramework */, + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -128,6 +136,9 @@ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( @@ -153,9 +164,12 @@ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -199,10 +213,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -252,6 +268,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -546,6 +563,18 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..5db441f 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..c30b367 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 1ac233d..1875d09 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,7 +68,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - diff --git a/example/pubspec.lock b/example/pubspec.lock index 2443d73..c998fd9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,58 +5,66 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "5bbf32bc9e518d41ec49718e2931cd4527292c9b0c6d2dffcf7fe6b9a8a8cf72" + url: "https://pub.dev" source: hosted version: "2.1.0" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -66,7 +74,8 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_test: @@ -79,53 +88,76 @@ packages: description: flutter source: sdk version: "0.0.0" - js: + 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: js - url: "https://pub.dartlang.org" + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "3.0.2" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3" + url: "https://pub.dev" source: hosted version: "2.0.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.13.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.18.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "075f927ebbab4262ace8d0b283929ac5410c0ac4e7fc123c76429564facfb757" + url: "https://pub.dev" source: hosted version: "2.1.2" privacy_screen: @@ -139,112 +171,135 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: d77dbb9d0b7469d91e42d352334b2b4bbd5cec4379542f1bdb630db368c4d9f6 + url: "https://pub.dev" source: hosted version: "1.8.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: dd11571b8a03f7cadcf91ec26a77e02bfbd6bbba2a512924d3116646b4198fc4 + url: "https://pub.dev" source: hosted version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a88162591b02c1f3a3db3af8ce1ea2b374bd75a7bb8d5e353bcfbdc79d719830 + url: "https://pub.dev" source: hosted version: "1.2.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.7.11" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: "4f0d5f9bf7efba3da5a7ff03bd33cc898c84bac978c068e1c94483828e709592" + url: "https://pub.dev" source: hosted version: "6.1.5" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + sha256: "1ccd353c1bff66b49863527c02759f4d06b92744bd9777c96a00ca6a9e8e1d2f" + url: "https://pub.dev" source: hosted version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + sha256: "6ba7dddee26c9fae27c9203c424631109d73c8fa26cfa7bc3e35e751cb87f62e" + url: "https://pub.dev" source: hosted version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "360fa359ab06bcb4f7c5cd3123a2a9a4d3364d4575d27c4b33468bd4497dd094" + url: "https://pub.dev" source: hosted version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: a9b3ea9043eabfaadfa3fb89de67a11210d85569086d22b3854484beab8b3978 + url: "https://pub.dev" source: hosted version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "80b860b31a11ebbcbe51b8fe887efc204f3af91522f3b51bcda4622d276d2120" + url: "https://pub.dev" source: hosted version: "2.1.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "5669882643b96bb6d5786637cac727c6e918a790053b09245fd4513b8a07df2a" + url: "https://pub.dev" source: hosted version: "2.0.13" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: e3c3b16d3104260c10eea3b0e34272aaa57921f83148b0619f74c2eced9b7ef1 + url: "https://pub.dev" source: hosted version: "3.0.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" sdks: - dart: ">=2.17.6 <3.0.0" - flutter: ">=2.10.0" + dart: ">=3.10.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/ios/.gitignore b/ios/.gitignore index 0c88507..bcb2d7d 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -9,6 +9,8 @@ profile DerivedData/ build/ +.build/ +.swiftpm/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m diff --git a/ios/Classes/PrivacyScreenPlugin.h b/ios/Classes/PrivacyScreenPlugin.h deleted file mode 100644 index f47fc06..0000000 --- a/ios/Classes/PrivacyScreenPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface PrivacyScreenPlugin : NSObject -@end diff --git a/ios/Classes/PrivacyScreenPlugin.m b/ios/Classes/PrivacyScreenPlugin.m deleted file mode 100644 index 8e45e8d..0000000 --- a/ios/Classes/PrivacyScreenPlugin.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "PrivacyScreenPlugin.h" -#if __has_include() -#import -#else -// Support project import fallback if the generated compatibility header -// is not copied when this plugin is created as a library. -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 -#import "privacy_screen-Swift.h" -#endif - -@implementation PrivacyScreenPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftPrivacyScreenPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/ios/privacy_screen.podspec b/ios/privacy_screen.podspec index b86161a..1ffa65c 100644 --- a/ios/privacy_screen.podspec +++ b/ios/privacy_screen.podspec @@ -13,9 +13,9 @@ A new Flutter plugin project. s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'privacy_screen/Sources/privacy_screen/**/*' s.dependency 'Flutter' - s.platform = :ios, '9.0' + s.platform = :ios, '13.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/ios/privacy_screen/Package.swift b/ios/privacy_screen/Package.swift new file mode 100644 index 0000000..6224b27 --- /dev/null +++ b/ios/privacy_screen/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "privacy_screen", + platforms: [ + .iOS("13.0") + ], + products: [ + // If the plugin name contains "_", replace with "-" for the library name. + .library(name: "privacy-screen", targets: ["privacy_screen"]) + ], + dependencies: [], + targets: [ + .target( + name: "privacy_screen", + dependencies: [] + ) + ] +) diff --git a/ios/Classes/SwiftPrivacyScreenPlugin.swift b/ios/privacy_screen/Sources/privacy_screen/PrivacyScreenPlugin.swift similarity index 86% rename from ios/Classes/SwiftPrivacyScreenPlugin.swift rename to ios/privacy_screen/Sources/privacy_screen/PrivacyScreenPlugin.swift index 2b316a4..6fbf08f 100644 --- a/ios/Classes/SwiftPrivacyScreenPlugin.swift +++ b/ios/privacy_screen/Sources/privacy_screen/PrivacyScreenPlugin.swift @@ -1,13 +1,15 @@ import Flutter import UIKit -public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { - +@objc(PrivacyScreenPlugin) +public class PrivacyScreenPlugin: NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate { + var enablePrivacy = false var lockWithDidEnterBackground = true var privacyImageName: String? var backgroundOpacity: CGFloat = 1 var backgroundColor: UIColor = UIColor.white + var backgroundColorDark: UIColor? = nil var backgroundTask: UIBackgroundTaskIdentifier! var privacyUIView: UIView? var isInFadeIn: Bool = false @@ -17,56 +19,57 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { var autoLockAfterSeconds: Double = -1 var blurEffect: UIBlurEffect.Style? var lockedDismissDelay: CFTimeInterval = 0.2 - - + + internal let registrar: FlutterPluginRegistrar - + init(registrar: FlutterPluginRegistrar) { self.registrar = registrar methodChannel = FlutterMethodChannel(name: "channel.couver.privacy_screen", binaryMessenger: registrar.messenger()) super.init() - registrar.addApplicationDelegate(self) } - + public static func register(with registrar: FlutterPluginRegistrar) { - let instance = SwiftPrivacyScreenPlugin(registrar: registrar) + let instance = PrivacyScreenPlugin(registrar: registrar) registrar.addMethodCallDelegate(instance, channel: instance.methodChannel) + registrar.addSceneDelegate(instance) } - + private func createPrivacyView() { - - if let window = UIApplication.shared.keyWindow { + + if let window = registrar.viewController?.view.window { dismissPrivacyView() privacyUIView = UIView(frame: window.bounds); privacyUIView?.alpha = 0.0 window.addSubview(privacyUIView!) - + if (blurEffect != nil && backgroundOpacity < 1) { - + let blurView = UIVisualEffectView( effect: UIBlurEffect( style: blurEffect! ) ) blurView.frame = window.bounds - + privacyUIView!.addSubview(blurView) } - + if (backgroundOpacity > 0) { let opacityView = UIView(frame: window.bounds) - opacityView.backgroundColor = backgroundColor + let isDark = window.traitCollection.userInterfaceStyle == .dark + opacityView.backgroundColor = (isDark && backgroundColorDark != nil) ? backgroundColorDark! : backgroundColor opacityView.alpha = backgroundOpacity privacyUIView!.addSubview(opacityView) } - + if ( !(privacyImageName ?? "").isEmpty) { let logoView = UIImageView(image: UIImage(named: privacyImageName!)) logoView.frame = window.bounds logoView.contentMode = .center privacyUIView!.addSubview(logoView) } - + isInFadeIn = true privacyUIView?.layer.removeAllAnimations() UIView.transition(with: privacyUIView!, @@ -79,7 +82,7 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { }) } } - + private func dismissPrivacyView() { guard privacyUIView != nil else { return @@ -91,14 +94,14 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { animations: { self.privacyUIView?.alpha = 0.0 }) { (finished) in - if finished && self.privacyUIView != nil && self.isInFadeIn { + if self.privacyUIView != nil { for subview in self.privacyUIView!.subviews { subview.removeFromSuperview() } self.privacyUIView?.removeFromSuperview() self.privacyUIView = nil } } } - + private func judgeLock() { let nowTime = NSDate().timeIntervalSince1970 if(autoLockAfterSeconds >= 0 && timeEnteredBackground > 0 && (nowTime - timeEnteredBackground) > autoLockAfterSeconds) { @@ -110,55 +113,57 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { dismissPrivacyView() } timeEnteredBackground = 0 - + } - - - public func applicationDidBecomeActive(_ application: UIApplication) { + + + // MARK: - UIScene lifecycle + + public func sceneDidBecomeActive(_ scene: UIScene) { methodChannel.invokeMethod("onLifeCycle", arguments: "applicationDidBecomeActive") judgeLock() } - - public func applicationDidEnterBackground(_ application: UIApplication) { - if(lockWithDidEnterBackground) { + + public func sceneDidEnterBackground(_ scene: UIScene) { + if lockWithDidEnterBackground { timeEnteredBackground = NSDate().timeIntervalSince1970 } methodChannel.invokeMethod("onLifeCycle", arguments: "applicationDidEnterBackground") } - - public func applicationWillEnterForeground(_ application: UIApplication) { + + public func sceneWillEnterForeground(_ scene: UIScene) { methodChannel.invokeMethod("onLifeCycle", arguments: "applicationWillEnterForeground") } - - public func applicationWillResignActive(_ application: UIApplication) { - if(!lockWithDidEnterBackground) { + + public func sceneWillResignActive(_ scene: UIScene) { + if !lockWithDidEnterBackground { timeEnteredBackground = NSDate().timeIntervalSince1970 } methodChannel.invokeMethod("onLifeCycle", arguments: "applicationWillResignActive") - if ( enablePrivacy ) { + if enablePrivacy { self.registerBackgroundTask() UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch() createPrivacyView() self.endBackgroundTask() } } - + func registerBackgroundTask() { self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in self?.endBackgroundTask() } assert(self.backgroundTask != UIBackgroundTaskIdentifier.invalid) } - + func endBackgroundTask() { UIApplication.shared.endBackgroundTask(backgroundTask) backgroundTask = UIBackgroundTaskIdentifier.invalid } - + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - + switch call.method { - + case "updateConfig": if let args = call.arguments as? Dictionary { if let backgroundOpacity = args["backgroundOpacity"] as? CGFloat { @@ -166,19 +171,25 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { } else { self.backgroundOpacity = 1 } - + if let privacyImageName = args["privacyImageName"] as? String { self.privacyImageName = privacyImageName } else { self.privacyImageName = nil } - + if let backgroundColor = args["backgroundColor"] as? String { self.backgroundColor = hexStringToUIColor(hex: backgroundColor) } else { self.backgroundColor = UIColor.white } - + + if let backgroundColorDark = args["backgroundColorDark"] as? String { + self.backgroundColorDark = hexStringToUIColor(hex: backgroundColorDark) + } else { + self.backgroundColorDark = nil + } + if let blurEffect = args["blurEffect"] as? String { switch blurEffect { case "light": @@ -193,19 +204,19 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { } else { self.blurEffect = nil } - + if let enablePrivacyIos = args["enablePrivacyIos"] as? Bool { self.enablePrivacy = enablePrivacyIos } else { self.enablePrivacy = false } - + if let autoLockAfterSecondsIos = args["autoLockAfterSecondsIos"] as? Double { self.autoLockAfterSeconds = autoLockAfterSecondsIos } else { self.autoLockAfterSeconds = -1 } - + if let lockWithDidEnterBackground = args["iosLockWithDidEnterBackground"] as? Bool { self.lockWithDidEnterBackground = lockWithDidEnterBackground } else { @@ -218,22 +229,22 @@ public class SwiftPrivacyScreenPlugin: NSObject, FlutterPlugin { result(FlutterMethodNotImplemented) } } - - + + func hexStringToUIColor (hex:String) -> UIColor { var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() - + if (cString.hasPrefix("#")) { cString.remove(at: cString.startIndex) } - + if ((cString.count) != 6) { return UIColor.gray } - + var rgbValue:UInt64 = 0 Scanner(string: cString).scanHexInt64(&rgbValue) - + return UIColor( red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, diff --git a/lib/src/privacy_screen.dart b/lib/src/privacy_screen.dart index a88703f..e22bf88 100644 --- a/lib/src/privacy_screen.dart +++ b/lib/src/privacy_screen.dart @@ -63,6 +63,7 @@ class PrivacyScreen { stateNotifier.value.androidOptions; PrivacyBlurEffect get blurEffect => stateNotifier.value.blurEffect; Color get backgroundColor => stateNotifier.value.backgroundColor; + Color? get backgroundColorDark => stateNotifier.value.backgroundColorDark; AppLifeCycle get appLifeCycle => lifeCycleNotifier.value; Future enable({ @@ -77,6 +78,10 @@ class PrivacyScreen { /// It can also be an translucent color Color backgroundColor = const Color(0xffffffff), + /// Dark mode background color. When provided and the device is in dark mode, + /// this color is used instead of [backgroundColor] on iOS. + Color? backgroundColorDark, + /// A blur effect for PrivacyView on IOS /// and the locker's background PrivacyBlurEffect blurEffect = PrivacyBlurEffect.extraLight, @@ -85,6 +90,7 @@ class PrivacyScreen { iosOptions: iosOptions, androidOptions: androidOptions, backgroundColor: backgroundColor, + backgroundColorDark: backgroundColorDark, blurEffect: blurEffect, ); if (result) { @@ -92,6 +98,7 @@ class PrivacyScreen { androidOptions: androidOptions, iosOptions: iosOptions, backgroundColor: backgroundColor, + backgroundColorDark: backgroundColorDark, blurEffect: blurEffect, ); } @@ -120,12 +127,14 @@ class PrivacyScreen { required PrivacyIosOptions iosOptions, required PrivacyAndroidOptions androidOptions, required Color backgroundColor, + Color? backgroundColorDark, required PrivacyBlurEffect blurEffect, }) async { final bool? result = await PrivacyScreenPlatform.instance.updateConfig( iosOptions: iosOptions, androidOptions: androidOptions, backgroundColor: backgroundColor, + backgroundColorDark: backgroundColorDark, blurEffect: blurEffect, ); return result ?? false; diff --git a/lib/src/privacy_screen_method_channel.dart b/lib/src/privacy_screen_method_channel.dart index 34b24c9..9c11ed8 100644 --- a/lib/src/privacy_screen_method_channel.dart +++ b/lib/src/privacy_screen_method_channel.dart @@ -16,25 +16,29 @@ class MethodChannelPrivacyScreen extends PrivacyScreenPlatform { required PrivacyIosOptions iosOptions, required PrivacyAndroidOptions androidOptions, required Color backgroundColor, + Color? backgroundColorDark, required PrivacyBlurEffect blurEffect, }) { double backgroundOpacity = backgroundColor.opacity; Color backgroundColorSolid = backgroundColor.withOpacity(1); - return methodChannel.invokeMethod( - 'updateConfig', - { - 'iosLockWithDidEnterBackground': - iosOptions.lockTrigger == IosLockTrigger.didEnterBackground, - 'privacyImageName': iosOptions.privacyImageName, - 'blurEffect': blurEffect.name, - 'backgroundColor': - '#${backgroundColorSolid.value.toRadixString(16).substring(2, 8)}', - 'backgroundOpacity': backgroundOpacity, - 'enablePrivacyIos': iosOptions.enablePrivacy, - 'autoLockAfterSecondsIos': iosOptions.autoLockAfterSeconds, - 'enableSecureAndroid': androidOptions.enableSecure, - 'autoLockAfterSecondsAndroid': androidOptions.autoLockAfterSeconds, - }, - ); + final Map args = { + 'iosLockWithDidEnterBackground': + iosOptions.lockTrigger == IosLockTrigger.didEnterBackground, + 'privacyImageName': iosOptions.privacyImageName, + 'blurEffect': blurEffect.name, + 'backgroundColor': + '#${backgroundColorSolid.value.toRadixString(16).substring(2, 8)}', + 'backgroundOpacity': backgroundOpacity, + 'enablePrivacyIos': iosOptions.enablePrivacy, + 'autoLockAfterSecondsIos': iosOptions.autoLockAfterSeconds, + 'enableSecureAndroid': androidOptions.enableSecure, + 'autoLockAfterSecondsAndroid': androidOptions.autoLockAfterSeconds, + }; + if (backgroundColorDark != null) { + final Color darkSolid = backgroundColorDark.withOpacity(1); + args['backgroundColorDark'] = + '#${darkSolid.value.toRadixString(16).substring(2, 8)}'; + } + return methodChannel.invokeMethod('updateConfig', args); } } diff --git a/lib/src/privacy_screen_platform_interface.dart b/lib/src/privacy_screen_platform_interface.dart index 87d1e9f..6c0b2b8 100644 --- a/lib/src/privacy_screen_platform_interface.dart +++ b/lib/src/privacy_screen_platform_interface.dart @@ -29,6 +29,7 @@ abstract class PrivacyScreenPlatform extends PlatformInterface { required PrivacyIosOptions iosOptions, required PrivacyAndroidOptions androidOptions, required Color backgroundColor, + Color? backgroundColorDark, required PrivacyBlurEffect blurEffect, }) { throw UnimplementedError('updateConfig() has not been implemented.'); diff --git a/lib/src/privacy_screen_state.dart b/lib/src/privacy_screen_state.dart index 33638ed..769fa93 100644 --- a/lib/src/privacy_screen_state.dart +++ b/lib/src/privacy_screen_state.dart @@ -3,18 +3,24 @@ import 'dart:ui' show Color; import 'package:flutter/foundation.dart'; import 'privacy_helpers.dart'; +// Sentinel used by PrivacyScreenState.copyWith to distinguish +// "caller did not provide a value" from an explicit null. +const Object _unset = Object(); + @immutable class PrivacyScreenState { final PrivacyIosOptions iosOptions; final PrivacyAndroidOptions androidOptions; final PrivacyBlurEffect blurEffect; final Color backgroundColor; + final Color? backgroundColorDark; const PrivacyScreenState({ this.iosOptions = const PrivacyIosOptions(), this.androidOptions = const PrivacyAndroidOptions(), this.blurEffect = PrivacyBlurEffect.extraLight, this.backgroundColor = const Color(0xFFFFFFFF), + this.backgroundColorDark, }); PrivacyScreenState copyWith({ @@ -23,12 +29,18 @@ class PrivacyScreenState { PrivacyAndroidOptions? androidOptions, PrivacyBlurEffect? blurEffect, Color? backgroundColor, + // Use [_unset] sentinel so explicit null clears the dark override, + // while omitting the parameter keeps the existing value. + Object? backgroundColorDark = _unset, }) { return PrivacyScreenState( androidOptions: androidOptions ?? this.androidOptions, iosOptions: iosOptions ?? this.iosOptions, blurEffect: blurEffect ?? this.blurEffect, backgroundColor: backgroundColor ?? this.backgroundColor, + backgroundColorDark: backgroundColorDark is Color? + ? backgroundColorDark + : this.backgroundColorDark, ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 3699460..15d78bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.0.6 homepage: https://github.com/eddyuan/privacy_screen.git environment: - sdk: ">=2.17.6 <3.0.0" + sdk: ">=2.17.6 <4.0.0" flutter: ">=2.5.0" dependencies: