diff --git a/config.lock.yaml b/config.lock.yaml
index 0c30c28..880e828 100644
--- a/config.lock.yaml
+++ b/config.lock.yaml
@@ -9,9 +9,11 @@ introduction_screen: 4a90e557630b28834479ed9c64a9d2d0185d8e48
libsignal_protocol_dart: 618f0c0b49534245a640a31d204265440cbac9ee
lottie: 4f1a5a52bdf1e1c1e12fa97c96174dcb05419e19
mutex: 84ca903a3ac863735e3228c75a212133621f680f
+no_screenshot: 8e19a8d0e30bd1d5000425cabac7ef3e3da4d5ea
optional: 71c638891ce4f2aff35c7387727989f31f9d877d
photo_view: a13ca2fc387a3fb1276126959e092c44d0029987
pointycastle: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
qr: ff808bb3f354e6a7029ec953cbe0144a42021db6
qr_flutter: d5e7206396105d643113618290bbcc755d05f492
+restart_app: 12339f63bf8e9631e619c4f9f6b4e013fa324715
x25519: ecb1d357714537bba6e276ef45f093846d4beaee
diff --git a/config.yaml b/config.yaml
index f46e879..65e243f 100644
--- a/config.yaml
+++ b/config.yaml
@@ -49,4 +49,10 @@ hand_signature:
git: https://github.com/RomanBase/hand_signature.git
flutter_sharing_intent:
- git: https://github.com/bhagat-techind/flutter_sharing_intent.git
\ No newline at end of file
+ git: https://github.com/bhagat-techind/flutter_sharing_intent.git
+
+restart_app:
+ git: https://github.com/gabrimatic/restart_app
+
+no_screenshot:
+ git: https://github.com/FlutterPlaza/no_screenshot.git
\ No newline at end of file
diff --git a/no_screenshot/LICENSE b/no_screenshot/LICENSE
new file mode 100644
index 0000000..8130832
--- /dev/null
+++ b/no_screenshot/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2022, FlutterPlaza
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+* Neither the name of FlutterPlaza nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/no_screenshot/android/.gitignore b/no_screenshot/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/no_screenshot/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/no_screenshot/android/build.gradle b/no_screenshot/android/build.gradle
new file mode 100644
index 0000000..b1c7bc7
--- /dev/null
+++ b/no_screenshot/android/build.gradle
@@ -0,0 +1,47 @@
+group 'com.flutterplaza.no_screenshot'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.6.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.1.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ namespace "com.flutterplaza.no_screenshot"
+ compileSdkVersion 31
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ minSdkVersion 16
+ }
+}
diff --git a/no_screenshot/android/gradle/wrapper/gradle-wrapper.jar b/no_screenshot/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..41d9927
Binary files /dev/null and b/no_screenshot/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/no_screenshot/android/gradle/wrapper/gradle-wrapper.properties b/no_screenshot/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..41dfb87
--- /dev/null
+++ b/no_screenshot/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/no_screenshot/android/gradlew b/no_screenshot/android/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/no_screenshot/android/gradlew
@@ -0,0 +1,234 @@
+#!/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.
+#
+
+##############################################################################
+#
+# 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/master/subprojects/plugins/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
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# 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"'
+
+# 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
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ 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
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# 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/no_screenshot/android/gradlew.bat b/no_screenshot/android/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/no_screenshot/android/gradlew.bat
@@ -0,0 +1,89 @@
+@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
+
+@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=.
+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 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.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+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%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/no_screenshot/android/settings.gradle b/no_screenshot/android/settings.gradle
new file mode 100644
index 0000000..8ed0d6a
--- /dev/null
+++ b/no_screenshot/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'no_screenshot'
diff --git a/no_screenshot/android/src/main/AndroidManifest.xml b/no_screenshot/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a2f47b6
--- /dev/null
+++ b/no_screenshot/android/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/no_screenshot/android/src/main/kotlin/com/flutterplaza/no_screenshot/NoScreenshotPlugin.kt b/no_screenshot/android/src/main/kotlin/com/flutterplaza/no_screenshot/NoScreenshotPlugin.kt
new file mode 100644
index 0000000..9ccb112
--- /dev/null
+++ b/no_screenshot/android/src/main/kotlin/com/flutterplaza/no_screenshot/NoScreenshotPlugin.kt
@@ -0,0 +1,199 @@
+package com.flutterplaza.no_screenshot
+
+import android.app.Activity
+import android.content.Context
+import android.content.SharedPreferences
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.MediaStore
+import android.util.Log
+import android.view.WindowManager.LayoutParams
+import androidx.annotation.NonNull
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.EventChannel
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import org.json.JSONObject
+
+const val SCREENSHOT_ON_CONST = "screenshotOn"
+const val SCREENSHOT_OFF_CONST = "screenshotOff"
+const val TOGGLE_SCREENSHOT_CONST = "toggleScreenshot"
+const val PREF_NAME = "screenshot_pref"
+const val START_SCREENSHOT_LISTENING_CONST = "startScreenshotListening"
+const val STOP_SCREENSHOT_LISTENING_CONST = "stopScreenshotListening"
+const val SCREENSHOT_PATH = "screenshot_path"
+const val PREF_KEY_SCREENSHOT = "is_screenshot_on"
+const val SCREENSHOT_TAKEN = "was_screenshot_taken"
+const val SCREENSHOT_METHOD_CHANNEL = "com.flutterplaza.no_screenshot_methods"
+const val SCREENSHOT_EVENT_CHANNEL = "com.flutterplaza.no_screenshot_streams"
+
+class NoScreenshotPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware, EventChannel.StreamHandler {
+ private lateinit var methodChannel: MethodChannel
+ private lateinit var eventChannel: EventChannel
+ private lateinit var context: Context
+ private var activity: Activity? = null
+ private lateinit var preferences: SharedPreferences
+ private var screenshotObserver: ContentObserver? = null
+ private val handler = Handler(Looper.getMainLooper())
+ private var eventSink: EventChannel.EventSink? = null
+ private var lastSharedPreferencesState: String = ""
+ private var hasSharedPreferencesChanged: Boolean = false
+
+ override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ context = flutterPluginBinding.applicationContext
+ preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+
+ methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, SCREENSHOT_METHOD_CHANNEL)
+ methodChannel.setMethodCallHandler(this)
+
+ eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, SCREENSHOT_EVENT_CHANNEL)
+ eventChannel.setStreamHandler(this)
+
+ initScreenshotObserver()
+ }
+
+ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+ methodChannel.setMethodCallHandler(null)
+ screenshotObserver?.let { context.contentResolver.unregisterContentObserver(it) }
+ }
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ restoreScreenshotState()
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {}
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ restoreScreenshotState()
+ }
+
+ override fun onDetachedFromActivity() {}
+
+ override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
+ when (call.method) {
+ SCREENSHOT_ON_CONST -> {
+ result.success(screenshotOn().also { updateSharedPreferencesState("") })
+ }
+ SCREENSHOT_OFF_CONST -> {
+ result.success(screenshotOff().also { updateSharedPreferencesState("") })
+ }
+ TOGGLE_SCREENSHOT_CONST -> {
+ toggleScreenshot()
+ result.success(true.also { updateSharedPreferencesState("") })
+ }
+ START_SCREENSHOT_LISTENING_CONST -> {
+ startListening()
+ result.success("Listening started")
+ }
+ STOP_SCREENSHOT_LISTENING_CONST -> {
+ stopListening()
+ result.success("Listening stopped".also { updateSharedPreferencesState("") })
+ }
+ else -> result.notImplemented()
+ }
+ }
+
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
+ eventSink = events
+ handler.postDelayed(screenshotStream, 1000)
+ }
+
+ override fun onCancel(arguments: Any?) {
+ handler.removeCallbacks(screenshotStream)
+ eventSink = null
+ }
+
+ private fun initScreenshotObserver() {
+ screenshotObserver = object : ContentObserver(Handler()) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ super.onChange(selfChange, uri)
+ uri?.let {
+ if (it.toString().contains(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
+ Log.d("ScreenshotProtection", "Screenshot detected")
+ updateSharedPreferencesState(it.path ?: "")
+ }
+ }
+ }
+ }
+ }
+
+ private fun startListening() {
+ screenshotObserver?.let {
+ context.contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, it)
+ }
+ }
+
+ private fun stopListening() {
+ screenshotObserver?.let { context.contentResolver.unregisterContentObserver(it) }
+ }
+
+ private fun screenshotOff(): Boolean = try {
+ activity?.window?.addFlags(LayoutParams.FLAG_SECURE)
+ saveScreenshotState(true)
+ true
+ } catch (e: Exception) {
+ false
+ }
+
+ private fun screenshotOn(): Boolean = try {
+ activity?.window?.clearFlags(LayoutParams.FLAG_SECURE)
+ saveScreenshotState(false)
+ true
+ } catch (e: Exception) {
+ false
+ }
+
+ private fun toggleScreenshot() {
+ activity?.window?.attributes?.flags?.let { flags ->
+ if (flags and LayoutParams.FLAG_SECURE != 0) {
+ screenshotOn()
+ } else {
+ screenshotOff()
+ }
+ }
+ }
+
+ private fun saveScreenshotState(isSecure: Boolean) {
+ preferences.edit().putBoolean(PREF_KEY_SCREENSHOT, isSecure).apply()
+ }
+
+ private fun restoreScreenshotState() {
+ val isSecure = preferences.getBoolean(PREF_KEY_SCREENSHOT, false)
+ if (isSecure) {
+ screenshotOff()
+ } else {
+ screenshotOn()
+ }
+ }
+
+ private fun updateSharedPreferencesState(screenshotData: String) {
+ val jsonString = convertMapToJsonString(mapOf(
+ PREF_KEY_SCREENSHOT to preferences.getBoolean(PREF_KEY_SCREENSHOT, false),
+ SCREENSHOT_PATH to screenshotData,
+ SCREENSHOT_TAKEN to screenshotData.isNotEmpty()
+ ))
+ if (lastSharedPreferencesState != jsonString) {
+ hasSharedPreferencesChanged = true
+ lastSharedPreferencesState = jsonString
+ }
+ }
+
+ private fun convertMapToJsonString(map: Map): String {
+ return JSONObject(map).toString()
+ }
+
+ private val screenshotStream = object : Runnable {
+ override fun run() {
+ if (hasSharedPreferencesChanged) {
+ eventSink?.success(lastSharedPreferencesState)
+ hasSharedPreferencesChanged = false
+ }
+ handler.postDelayed(this, 1000)
+ }
+ }
+}
\ No newline at end of file
diff --git a/no_screenshot/android/src/test/kotlin/com/flutterplaza/no_screenshot/NoScreenshotPluginTest.kt b/no_screenshot/android/src/test/kotlin/com/flutterplaza/no_screenshot/NoScreenshotPluginTest.kt
new file mode 100644
index 0000000..fe068a2
--- /dev/null
+++ b/no_screenshot/android/src/test/kotlin/com/flutterplaza/no_screenshot/NoScreenshotPluginTest.kt
@@ -0,0 +1,27 @@
+package com.flutterplaza.no_screenshot
+
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import kotlin.test.Test
+import org.mockito.Mockito
+
+/*
+ * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
+ *
+ * Once you have built the plugin's example app, you can run these tests from the command
+ * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
+ * you can run them directly from IDEs that support JUnit such as Android Studio.
+ */
+
+internal class NoScreenshotPluginTest {
+ @Test
+ fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
+ val plugin = NoScreenshotPlugin()
+
+ val call = MethodCall("getPlatformVersion", null)
+ val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
+ plugin.onMethodCall(call, mockResult)
+
+ Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
+ }
+}
diff --git a/no_screenshot/ios/.gitignore b/no_screenshot/ios/.gitignore
new file mode 100644
index 0000000..0c88507
--- /dev/null
+++ b/no_screenshot/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/no_screenshot/ios/Assets/.gitkeep b/no_screenshot/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/no_screenshot/ios/Classes/IOSNoScreenshotPlugin.swift b/no_screenshot/ios/Classes/IOSNoScreenshotPlugin.swift
new file mode 100644
index 0000000..ae360d6
--- /dev/null
+++ b/no_screenshot/ios/Classes/IOSNoScreenshotPlugin.swift
@@ -0,0 +1,182 @@
+import Flutter
+import UIKit
+import ScreenProtectorKit
+
+public class IOSNoScreenshotPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
+ private var screenProtectorKit: ScreenProtectorKit? = nil
+ private static var methodChannel: FlutterMethodChannel? = nil
+ private static var eventChannel: FlutterEventChannel? = nil
+ private static var preventScreenShot: Bool = false
+ private var eventSink: FlutterEventSink? = nil
+ private var lastSharedPreferencesState: String = ""
+ private var hasSharedPreferencesChanged: Bool = false
+
+ private static let ENABLESCREENSHOT = false
+ private static let DISABLESCREENSHOT = true
+
+ private static let preventScreenShotKey = "preventScreenShot"
+ private static let methodChannelName = "com.flutterplaza.no_screenshot_methods"
+ private static let eventChannelName = "com.flutterplaza.no_screenshot_streams"
+ private static let screenshotPathPlaceholder = "screenshot_path_placeholder"
+
+ init(screenProtectorKit: ScreenProtectorKit) {
+ self.screenProtectorKit = screenProtectorKit
+ super.init()
+
+ // Restore the saved state from UserDefaults
+ var fetchVal = UserDefaults.standard.bool(forKey: IOSNoScreenshotPlugin.preventScreenShotKey)
+ updateScreenshotState(isScreenshotBlocked: fetchVal)
+ }
+
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ methodChannel = FlutterMethodChannel(name: methodChannelName, binaryMessenger: registrar.messenger())
+ eventChannel = FlutterEventChannel(name: eventChannelName, binaryMessenger: registrar.messenger())
+
+ let window = UIApplication.shared.delegate?.window
+ let screenProtectorKit = ScreenProtectorKit(window: window as? UIWindow)
+ screenProtectorKit.configurePreventionScreenshot()
+
+ let instance = IOSNoScreenshotPlugin(screenProtectorKit: screenProtectorKit)
+ registrar.addMethodCallDelegate(instance, channel: methodChannel!)
+ eventChannel?.setStreamHandler(instance)
+ registrar.addApplicationDelegate(instance)
+ }
+
+ public func applicationWillResignActive(_ application: UIApplication) {
+ persistState()
+ }
+
+ public func applicationDidBecomeActive(_ application: UIApplication) {
+ fetchPersistedState()
+ }
+
+ public func applicationWillEnterForeground(_ application: UIApplication) {
+ fetchPersistedState()
+ }
+
+ public func applicationDidEnterBackground(_ application: UIApplication) {
+ persistState()
+ }
+
+ public func applicationWillTerminate(_ application: UIApplication) {
+ persistState()
+ }
+
+ func persistState() {
+ // Persist the state when changed
+ UserDefaults.standard.set(IOSNoScreenshotPlugin.preventScreenShot, forKey: IOSNoScreenshotPlugin.preventScreenShotKey)
+ print("Persisted state: \(IOSNoScreenshotPlugin.preventScreenShot)")
+ updateSharedPreferencesState("")
+ }
+
+ func fetchPersistedState() {
+ // Restore the saved state from UserDefaults
+ var fetchVal = UserDefaults.standard.bool(forKey: IOSNoScreenshotPlugin.preventScreenShotKey) ? IOSNoScreenshotPlugin.DISABLESCREENSHOT :IOSNoScreenshotPlugin.ENABLESCREENSHOT
+ updateScreenshotState(isScreenshotBlocked: fetchVal)
+ print("Fetched state: \(IOSNoScreenshotPlugin.preventScreenShot)")
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "screenshotOff":
+ shotOff()
+ result(true)
+ case "screenshotOn":
+ shotOn()
+ result(true)
+ case "toggleScreenshot":
+ IOSNoScreenshotPlugin.preventScreenShot ? shotOn(): shotOff()
+ result(true)
+ case "startScreenshotListening":
+ startListening()
+ result("Listening started")
+ case "stopScreenshotListening":
+ stopListening()
+ result("Listening stopped")
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
+
+ private func shotOff() {
+ IOSNoScreenshotPlugin.preventScreenShot = IOSNoScreenshotPlugin.DISABLESCREENSHOT
+ screenProtectorKit?.enabledPreventScreenshot()
+ persistState()
+ }
+
+ private func shotOn() {
+ IOSNoScreenshotPlugin.preventScreenShot = IOSNoScreenshotPlugin.ENABLESCREENSHOT
+ screenProtectorKit?.disablePreventScreenshot()
+ persistState()
+ }
+
+ private func startListening() {
+ NotificationCenter.default.addObserver(self, selector: #selector(screenshotDetected), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
+ persistState()
+ }
+
+ private func stopListening() {
+ NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil)
+ persistState()
+ }
+
+ @objc private func screenshotDetected() {
+ print("Screenshot detected")
+ updateSharedPreferencesState(IOSNoScreenshotPlugin.screenshotPathPlaceholder)
+ }
+
+ private func updateScreenshotState(isScreenshotBlocked: Bool) {
+ if isScreenshotBlocked {
+ screenProtectorKit?.enabledPreventScreenshot()
+ } else {
+ screenProtectorKit?.disablePreventScreenshot()
+ }
+ }
+
+ private func updateSharedPreferencesState(_ screenshotData: String) {
+ let map: [String: Any] = [
+ "is_screenshot_on": IOSNoScreenshotPlugin.preventScreenShot,
+ "screenshot_path": screenshotData,
+ "was_screenshot_taken": !screenshotData.isEmpty
+ ]
+ let jsonString = convertMapToJsonString(map)
+ if lastSharedPreferencesState != jsonString {
+ hasSharedPreferencesChanged = true
+ lastSharedPreferencesState = jsonString
+ }
+ }
+
+ private func convertMapToJsonString(_ map: [String: Any]) -> String {
+ if let jsonData = try? JSONSerialization.data(withJSONObject: map, options: .prettyPrinted) {
+ return String(data: jsonData, encoding: .utf8) ?? ""
+ }
+ return ""
+ }
+
+ public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
+ eventSink = events
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+ self.screenshotStream()
+ }
+ return nil
+ }
+
+ public func onCancel(withArguments arguments: Any?) -> FlutterError? {
+ eventSink = nil
+ return nil
+ }
+
+ private func screenshotStream() {
+ if hasSharedPreferencesChanged {
+ eventSink?(lastSharedPreferencesState)
+ hasSharedPreferencesChanged = false
+ }
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+ self.screenshotStream()
+ }
+ }
+
+ deinit {
+ screenProtectorKit?.removeAllObserver()
+ }
+}
diff --git a/no_screenshot/ios/Classes/NoScreenshotPlugin.h b/no_screenshot/ios/Classes/NoScreenshotPlugin.h
new file mode 100644
index 0000000..0198b60
--- /dev/null
+++ b/no_screenshot/ios/Classes/NoScreenshotPlugin.h
@@ -0,0 +1,4 @@
+#import
+
+@interface NoScreenshotPlugin : NSObject
+@end
diff --git a/no_screenshot/ios/Classes/NoScreenshotPlugin.m b/no_screenshot/ios/Classes/NoScreenshotPlugin.m
new file mode 100644
index 0000000..f639229
--- /dev/null
+++ b/no_screenshot/ios/Classes/NoScreenshotPlugin.m
@@ -0,0 +1,15 @@
+#import "NoScreenshotPlugin.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 "no_screenshot-Swift.h"
+#endif
+
+@implementation NoScreenshotPlugin
++ (void)registerWithRegistrar:(NSObject*)registrar {
+ [IOSNoScreenshotPlugin registerWithRegistrar:registrar];
+}
+@end
diff --git a/no_screenshot/ios/Resources/PrivacyInfo.xcprivacy b/no_screenshot/ios/Resources/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000..a34b7e2
--- /dev/null
+++ b/no_screenshot/ios/Resources/PrivacyInfo.xcprivacy
@@ -0,0 +1,14 @@
+
+
+
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyAccessedAPITypes
+
+ NSPrivacyCollectedDataTypes
+
+ NSPrivacyTracking
+
+
+
diff --git a/no_screenshot/ios/no_screenshot.podspec b/no_screenshot/ios/no_screenshot.podspec
new file mode 100644
index 0000000..7d9027e
--- /dev/null
+++ b/no_screenshot/ios/no_screenshot.podspec
@@ -0,0 +1,26 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint no_screenshot.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'no_screenshot'
+ s.version = '0.3.2-beta.3'
+ s.summary = 'Flutter plugin to enable, disable or toggle screenshot support in your application.'
+ s.description = <<-DESC
+A new Flutter plugin project.
+ DESC
+ s.homepage = 'https://github.com/FlutterPlaza/no_screenshot'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'FlutterPlaza' => 'dev@flutterplaza.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.dependency 'Flutter'
+ # Updated the dependency version to remove the wildcard and use a specific version range
+ s.dependency 'ScreenProtectorKit', '~> 1.3.1'
+ s.platform = :ios, '10.0'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ # Updated swift_version to a single version as an array is not supported for this attribute
+ s.swift_version = "5.0"
+end
diff --git a/no_screenshot/lib/constants.dart b/no_screenshot/lib/constants.dart
new file mode 100644
index 0000000..f2e5e59
--- /dev/null
+++ b/no_screenshot/lib/constants.dart
@@ -0,0 +1,7 @@
+const screenShotOnConst = "screenshotOn";
+const screenShotOffConst = "screenshotOff";
+const toggleScreenShotConst = "toggleScreenshot";
+const startScreenshotListeningConst = 'startScreenshotListening';
+const stopScreenshotListeningConst = 'stopScreenshotListening';
+const screenshotMethodChannel = "com.flutterplaza.no_screenshot_methods";
+const screenshotEventChannel = "com.flutterplaza.no_screenshot_streams";
diff --git a/no_screenshot/lib/no_screenshot.dart b/no_screenshot/lib/no_screenshot.dart
new file mode 100644
index 0000000..f9469ad
--- /dev/null
+++ b/no_screenshot/lib/no_screenshot.dart
@@ -0,0 +1,74 @@
+import 'package:no_screenshot/screenshot_snapshot.dart';
+
+import 'no_screenshot_platform_interface.dart';
+
+/// A class that provides a platform-agnostic way to disable screenshots.
+///
+class NoScreenshot implements NoScreenshotPlatform {
+ final _instancePlatform = NoScreenshotPlatform.instance;
+ NoScreenshot._();
+
+ @Deprecated(
+ "Using this may cause issue\nUse instance directly\ne.g: 'NoScreenshot.instance.screenshotOff()'")
+ NoScreenshot();
+
+ static NoScreenshot get instance => NoScreenshot._();
+
+ /// Return `true` if screenshot capabilities has been
+ /// successfully disabled or is currently disabled and `false` otherwise.
+ /// throw `UnmimplementedError` if not implement
+ ///
+ @override
+ Future screenshotOff() {
+ return _instancePlatform.screenshotOff();
+ }
+
+ /// Return `true` if screenshot capabilities has been
+ /// successfully enabled or is currently enabled and `false` otherwise.
+ /// throw `UnmimplementedError` if not implement
+ ///
+ @override
+ Future screenshotOn() {
+ return _instancePlatform.screenshotOn();
+ }
+
+ /// Return `true` if screenshot capabilities has been
+ /// successfully toggle from it previous state and `false` if the attempt
+ /// to toggle failed.
+ /// throw `UnmimplementedError` if not implement
+ ///
+ @override
+ Future toggleScreenshot() {
+ return _instancePlatform.toggleScreenshot();
+ }
+
+ /// Stream to screenshot activities [ScreenshotSnapshot]
+ ///
+ @override
+ Stream get screenshotStream {
+ return _instancePlatform.screenshotStream;
+ }
+
+ /// Start listening to screenshot activities
+ @override
+ Future startScreenshotListening() {
+ return _instancePlatform.startScreenshotListening();
+ }
+
+ /// Stop listening to screenshot activities
+ @override
+ Future stopScreenshotListening() {
+ return _instancePlatform.stopScreenshotListening();
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ other is NoScreenshot &&
+ runtimeType == other.runtimeType &&
+ _instancePlatform == other._instancePlatform;
+ }
+
+ @override
+ int get hashCode => _instancePlatform.hashCode;
+}
diff --git a/no_screenshot/lib/no_screenshot_method_channel.dart b/no_screenshot/lib/no_screenshot_method_channel.dart
new file mode 100644
index 0000000..6633075
--- /dev/null
+++ b/no_screenshot/lib/no_screenshot_method_channel.dart
@@ -0,0 +1,52 @@
+import 'dart:convert';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:no_screenshot/constants.dart';
+import 'package:no_screenshot/screenshot_snapshot.dart';
+
+import 'no_screenshot_platform_interface.dart';
+
+/// An implementation of [NoScreenshotPlatform] that uses method channels.
+class MethodChannelNoScreenshot extends NoScreenshotPlatform {
+ /// The method channel used to interact with the native platform.
+ @visibleForTesting
+ final methodChannel = const MethodChannel(screenshotMethodChannel);
+ @visibleForTesting
+ final eventChannel = const EventChannel(screenshotEventChannel);
+
+ @override
+ Stream get screenshotStream {
+ return eventChannel.receiveBroadcastStream().map((event) =>
+ ScreenshotSnapshot.fromMap(jsonDecode(event) as Map));
+ }
+
+ @override
+ Future toggleScreenshot() async {
+ final result =
+ await methodChannel.invokeMethod(toggleScreenShotConst);
+ return result ?? false;
+ }
+
+ @override
+ Future screenshotOff() async {
+ final result = await methodChannel.invokeMethod(screenShotOffConst);
+ return result ?? false;
+ }
+
+ @override
+ Future screenshotOn() async {
+ final result = await methodChannel.invokeMethod(screenShotOnConst);
+ return result ?? false;
+ }
+
+ @override
+ Future startScreenshotListening() {
+ return methodChannel.invokeMethod(startScreenshotListeningConst);
+ }
+
+ @override
+ Future stopScreenshotListening() {
+ return methodChannel.invokeMethod(stopScreenshotListeningConst);
+ }
+}
diff --git a/no_screenshot/lib/no_screenshot_platform_interface.dart b/no_screenshot/lib/no_screenshot_platform_interface.dart
new file mode 100644
index 0000000..eac1732
--- /dev/null
+++ b/no_screenshot/lib/no_screenshot_platform_interface.dart
@@ -0,0 +1,68 @@
+import 'package:no_screenshot/screenshot_snapshot.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+import 'no_screenshot_method_channel.dart';
+
+abstract class NoScreenshotPlatform extends PlatformInterface {
+ /// Constructs a NoScreenshotPlatform.
+ NoScreenshotPlatform() : super(token: _token);
+
+ static final Object _token = Object();
+
+ static NoScreenshotPlatform _instance = MethodChannelNoScreenshot();
+
+ /// The default instance of [NoScreenshotPlatform] to use.
+ ///
+ /// Defaults to [MethodChannelNoScreenshot].
+ static NoScreenshotPlatform get instance => _instance;
+
+ /// Platform-specific implementations should set this with their own
+ /// platform-specific class that extends [NoScreenshotPlatform] when
+ /// they register themselves.
+ static set instance(NoScreenshotPlatform instance) {
+ PlatformInterface.verifyToken(instance, _token);
+ _instance = instance;
+ }
+
+ /// Return `true` if screenshot capabilities has been
+ /// successfully disabled or is currently disabled and `false` otherwise.
+ /// throw `UnmimplementedError` if not implement
+ Future screenshotOff() {
+ throw UnimplementedError('screenshotOff() has not been implemented.');
+ }
+
+ /// Return `true` if screenshot capabilities has been
+ /// successfully enabled or is currently enabled and `false` otherwise.
+ /// throw `UnmimplementedError` if not implement
+ Future screenshotOn() {
+ throw UnimplementedError('screenshotOn() has not been implemented.');
+ }
+
+ /// Return `true` if screenshot capabilities has been
+ /// successfully toggle from it previous state and `false` if the attempt
+ /// to toggle failed.
+ /// throw `UnmimplementedError` if not implement
+ Future toggleScreenshot() {
+ throw UnimplementedError('toggleScreenshot() has not been implemented.');
+ }
+
+ /// Stream to screenshot activities [ScreenshotSnapshot]
+ /// This stream will emit a [ScreenshotSnapshot] whenever a screenshot is taken.
+ /// The [ScreenshotSnapshot] contains the path to the screenshot file.
+ /// throw `UnmimplementedError` if not implement
+ Stream get screenshotStream {
+ throw UnimplementedError('incrementStream has not been implemented.');
+ }
+
+// Start listening to screenshot activities
+ Future startScreenshotListening() {
+ throw UnimplementedError(
+ 'startScreenshotListening has not been implemented.');
+ }
+
+ /// Stop listening to screenshot activities
+ Future stopScreenshotListening() {
+ throw UnimplementedError(
+ 'stopScreenshotListening has not been implemented.');
+ }
+}
diff --git a/no_screenshot/lib/screenshot_snapshot.dart b/no_screenshot/lib/screenshot_snapshot.dart
new file mode 100644
index 0000000..6aa20e8
--- /dev/null
+++ b/no_screenshot/lib/screenshot_snapshot.dart
@@ -0,0 +1,49 @@
+class ScreenshotSnapshot {
+ final String screenshotPath;
+ final bool isScreenshotProtectionOn;
+ final bool wasScreenshotTaken;
+
+ ScreenshotSnapshot({
+ required this.screenshotPath,
+ required this.isScreenshotProtectionOn,
+ required this.wasScreenshotTaken,
+ });
+
+ factory ScreenshotSnapshot.fromMap(Map map) {
+ return ScreenshotSnapshot(
+ screenshotPath: map['screenshot_path'] as String? ?? '',
+ isScreenshotProtectionOn: map['is_screenshot_on'] as bool? ?? false,
+ wasScreenshotTaken: map['was_screenshot_taken'] as bool? ?? false,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'screenshot_path': screenshotPath,
+ 'is_screenshot_on': isScreenshotProtectionOn,
+ 'was_screenshot_taken': wasScreenshotTaken,
+ };
+ }
+
+ @override
+ String toString() {
+ return 'ScreenshotSnapshot(\nscreenshotPath: $screenshotPath, \nisScreenshotProtectionOn: $isScreenshotProtectionOn, \nwasScreenshotTaken: $wasScreenshotTaken\n)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+
+ return other is ScreenshotSnapshot &&
+ other.screenshotPath == screenshotPath &&
+ other.isScreenshotProtectionOn == isScreenshotProtectionOn &&
+ other.wasScreenshotTaken == wasScreenshotTaken;
+ }
+
+ @override
+ int get hashCode {
+ return screenshotPath.hashCode ^
+ isScreenshotProtectionOn.hashCode ^
+ wasScreenshotTaken.hashCode;
+ }
+}
diff --git a/no_screenshot/pubspec.yaml b/no_screenshot/pubspec.yaml
new file mode 100644
index 0000000..23a142f
--- /dev/null
+++ b/no_screenshot/pubspec.yaml
@@ -0,0 +1,32 @@
+name: no_screenshot
+description: Flutter plugin to enable, disable, toggle or stream screenshot activities in your application.
+version: 0.3.2-beta.3
+homepage: https://flutterplaza.com
+repository: https://github.com/FlutterPlaza/no_screenshot/releases/tag/v0.3.2-beta.3
+
+environment:
+ sdk: '>=3.0.0 <4.0.0'
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ plugin_platform_interface: ^2.1.8
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^4.0.0
+ flutter_driver:
+ sdk: flutter
+
+flutter:
+ plugin:
+ platforms:
+ android:
+ package: com.flutterplaza.no_screenshot
+ pluginClass: NoScreenshotPlugin
+ ios:
+ pluginClass: NoScreenshotPlugin
+ macos:
+ pluginClass: MacOSNoScreenshotPlugin
diff --git a/no_screenshot/test/no_screenshot_method_channel_test.dart b/no_screenshot/test/no_screenshot_method_channel_test.dart
new file mode 100644
index 0000000..1033ec8
--- /dev/null
+++ b/no_screenshot/test/no_screenshot_method_channel_test.dart
@@ -0,0 +1,166 @@
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:no_screenshot/constants.dart';
+import 'package:no_screenshot/no_screenshot_method_channel.dart';
+import 'package:no_screenshot/screenshot_snapshot.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ late MethodChannelNoScreenshot platform;
+
+ setUp(() {
+ platform = MethodChannelNoScreenshot();
+ });
+
+ group('MethodChannelNoScreenshot', () {
+ const MethodChannel channel = MethodChannel(screenshotMethodChannel);
+
+ test('screenshotOn', () async {
+ const bool expected = true;
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
+ if (methodCall.method == screenShotOnConst) {
+ return expected;
+ }
+ return null;
+ });
+
+ final result = await platform.screenshotOn();
+ expect(result, expected);
+ });
+
+ test('screenshotOff', () async {
+ const bool expected = true;
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
+ if (methodCall.method == screenShotOffConst) {
+ return expected;
+ }
+ return null;
+ });
+
+ final result = await platform.screenshotOff();
+ expect(result, expected);
+ });
+
+ test('toggleScreenshot', () async {
+ const bool expected = true;
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
+ if (methodCall.method == toggleScreenShotConst) {
+ return expected;
+ }
+ return null;
+ });
+
+ final result = await platform.toggleScreenshot();
+ expect(result, expected);
+ });
+
+ test('startScreenshotListening', () async {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
+ if (methodCall.method == startScreenshotListeningConst) {
+ return null;
+ }
+ return null;
+ });
+
+ await platform.startScreenshotListening();
+ expect(true, true); // Add more specific expectations if needed
+ });
+
+ test('stopScreenshotListening', () async {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
+ if (methodCall.method == stopScreenshotListeningConst) {
+ return null;
+ }
+ return null;
+ });
+
+ await platform.stopScreenshotListening();
+ expect(true, true); // Add more specific expectations if needed
+ });
+ });
+
+ group('ScreenshotSnapshot', () {
+ test('fromMap', () {
+ final map = {
+ 'screenshot_path': '/example/path',
+ 'is_screenshot_on': true,
+ 'was_screenshot_taken': true,
+ };
+ final snapshot = ScreenshotSnapshot.fromMap(map);
+ expect(snapshot.screenshotPath, '/example/path');
+ expect(snapshot.isScreenshotProtectionOn, true);
+ expect(snapshot.wasScreenshotTaken, true);
+ });
+
+ test('toMap', () {
+ final snapshot = ScreenshotSnapshot(
+ screenshotPath: '/example/path',
+ isScreenshotProtectionOn: true,
+ wasScreenshotTaken: true,
+ );
+ final map = snapshot.toMap();
+ expect(map['screenshot_path'], '/example/path');
+ expect(map['is_screenshot_on'], true);
+ expect(map['was_screenshot_taken'], true);
+ });
+
+ test('equality operator', () {
+ final snapshot1 = ScreenshotSnapshot(
+ screenshotPath: '/example/path',
+ isScreenshotProtectionOn: true,
+ wasScreenshotTaken: true,
+ );
+ final snapshot2 = ScreenshotSnapshot(
+ screenshotPath: '/example/path',
+ isScreenshotProtectionOn: true,
+ wasScreenshotTaken: true,
+ );
+ final snapshot3 = ScreenshotSnapshot(
+ screenshotPath: '/different/path',
+ isScreenshotProtectionOn: false,
+ wasScreenshotTaken: false,
+ );
+
+ expect(snapshot1 == snapshot2, true);
+ expect(snapshot1 == snapshot3, false);
+ });
+
+ test('hashCode', () {
+ final snapshot1 = ScreenshotSnapshot(
+ screenshotPath: '/example/path',
+ isScreenshotProtectionOn: true,
+ wasScreenshotTaken: true,
+ );
+ final snapshot2 = ScreenshotSnapshot(
+ screenshotPath: '/example/path',
+ isScreenshotProtectionOn: true,
+ wasScreenshotTaken: true,
+ );
+ final snapshot3 = ScreenshotSnapshot(
+ screenshotPath: '/different/path',
+ isScreenshotProtectionOn: false,
+ wasScreenshotTaken: false,
+ );
+
+ expect(snapshot1.hashCode, snapshot2.hashCode);
+ expect(snapshot1.hashCode, isNot(snapshot3.hashCode));
+ });
+
+ test('toString', () {
+ final snapshot = ScreenshotSnapshot(
+ screenshotPath: '/example/path',
+ isScreenshotProtectionOn: true,
+ wasScreenshotTaken: true,
+ );
+ final string = snapshot.toString();
+ expect(string,
+ 'ScreenshotSnapshot(\nscreenshotPath: /example/path, \nisScreenshotProtectionOn: true, \nwasScreenshotTaken: true\n)');
+ });
+ });
+}
diff --git a/no_screenshot/test/no_screenshot_platform_interface_test.dart b/no_screenshot/test/no_screenshot_platform_interface_test.dart
new file mode 100644
index 0000000..ab0a9d4
--- /dev/null
+++ b/no_screenshot/test/no_screenshot_platform_interface_test.dart
@@ -0,0 +1,75 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:no_screenshot/no_screenshot_method_channel.dart';
+import 'package:no_screenshot/no_screenshot_platform_interface.dart';
+import 'package:no_screenshot/screenshot_snapshot.dart';
+
+class MockNoScreenshotPlatform extends NoScreenshotPlatform {
+ @override
+ Future screenshotOff() async {
+ return true;
+ }
+
+ @override
+ Future screenshotOn() async {
+ return true;
+ }
+
+ @override
+ Future toggleScreenshot() async {
+ return true;
+ }
+
+ @override
+ Stream get screenshotStream {
+ return const Stream.empty();
+ }
+
+ @override
+ Future startScreenshotListening() async {
+ return;
+ }
+
+ @override
+ Future stopScreenshotListening() async {
+ return;
+ }
+}
+
+void main() {
+ final platform = MockNoScreenshotPlatform();
+
+ group('NoScreenshotPlatform', () {
+ test('default instance should be MethodChannelNoScreenshot', () {
+ expect(NoScreenshotPlatform.instance,
+ isInstanceOf());
+ });
+
+ test('screenshotOff should return true when called', () async {
+ expect(await platform.screenshotOff(), isTrue);
+ });
+
+ test('screenshotOn should return true when called', () async {
+ expect(await platform.screenshotOn(), isTrue);
+ });
+
+ test('toggleScreenshot should return true when called', () async {
+ expect(await platform.toggleScreenshot(), isTrue);
+ });
+
+ test('screenshotStream should not throw UnimplementedError when accessed',
+ () {
+ expect(() => platform.screenshotStream, isNot(throwsUnimplementedError));
+ });
+ test(
+ 'startScreenshotListening should not throw UnimplementedError when called',
+ () async {
+ expect(platform.startScreenshotListening(), completes);
+ });
+
+ test(
+ 'stopScreenshotListening should not throw UnimplementedError when called',
+ () async {
+ expect(platform.stopScreenshotListening(), completes);
+ });
+ });
+}
diff --git a/no_screenshot/test/no_screenshot_test.dart b/no_screenshot/test/no_screenshot_test.dart
new file mode 100644
index 0000000..86396bd
--- /dev/null
+++ b/no_screenshot/test/no_screenshot_test.dart
@@ -0,0 +1,95 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:no_screenshot/no_screenshot_platform_interface.dart';
+import 'package:no_screenshot/no_screenshot_method_channel.dart';
+import 'package:no_screenshot/screenshot_snapshot.dart';
+import 'package:no_screenshot/no_screenshot.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+class MockNoScreenshotPlatform
+ with MockPlatformInterfaceMixin
+ implements NoScreenshotPlatform {
+ @override
+ Future screenshotOff() async {
+ // Mock implementation or return a fixed value
+ return Future.value(true);
+ }
+
+ @override
+ Future screenshotOn() async {
+ // Mock implementation or return a fixed value
+ return Future.value(true);
+ }
+
+ @override
+ Future toggleScreenshot() async {
+ // Mock implementation or return a fixed value
+ return Future.value(true);
+ }
+
+ @override
+ Stream get screenshotStream => const Stream.empty();
+
+ @override
+ Future startScreenshotListening() {
+ return Future.value();
+ }
+
+ @override
+ Future stopScreenshotListening() {
+ return Future.value();
+ }
+}
+
+void main() {
+ final NoScreenshotPlatform initialPlatform = NoScreenshotPlatform.instance;
+ MockNoScreenshotPlatform fakePlatform = MockNoScreenshotPlatform();
+
+ setUp(() {
+ NoScreenshotPlatform.instance = fakePlatform;
+ });
+
+ tearDown(() {
+ NoScreenshotPlatform.instance = initialPlatform;
+ });
+
+ test('$MethodChannelNoScreenshot is the default instance', () {
+ expect(initialPlatform, isInstanceOf());
+ });
+
+ test('NoScreenshot instance is a singleton', () {
+ final instance1 = NoScreenshot.instance;
+ final instance2 = NoScreenshot.instance;
+ expect(instance1, equals(instance2));
+ });
+
+ test('screenshotOn', () async {
+ expect(await NoScreenshot.instance.screenshotOn(), true);
+ });
+
+ test('screenshotOff', () async {
+ expect(await NoScreenshot.instance.screenshotOff(), true);
+ });
+
+ test('toggleScreenshot', () async {
+ expect(await NoScreenshot.instance.toggleScreenshot(), true);
+ });
+
+ test('screenshotStream', () async {
+ expect(NoScreenshot.instance.screenshotStream,
+ isInstanceOf>());
+ });
+ test('startScreenshotListening', () async {
+ expect(NoScreenshot.instance.startScreenshotListening(), completes);
+ });
+
+ test('stopScreenshotListening', () async {
+ expect(NoScreenshot.instance.stopScreenshotListening(), completes);
+ });
+
+ test('NoScreenshot equality operator', () {
+ final instance1 = NoScreenshot.instance;
+ final instance2 = NoScreenshot.instance;
+
+ expect(instance1 == instance2, true, reason: 'Instances should be equal');
+ });
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 256377a..9106396 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -21,6 +21,8 @@ dependency_overrides:
path: ./dependencies/lottie
mutex:
path: ./dependencies/mutex
+ no_screenshot:
+ path: ./dependencies/no_screenshot
optional:
path: ./dependencies/optional
photo_view:
@@ -31,5 +33,7 @@ dependency_overrides:
path: ./dependencies/qr
qr_flutter:
path: ./dependencies/qr_flutter
+ restart_app:
+ path: ./dependencies/restart_app
x25519:
path: ./dependencies/x25519
diff --git a/restart_app/LICENSE b/restart_app/LICENSE
new file mode 100644
index 0000000..58e9821
--- /dev/null
+++ b/restart_app/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Hossein Yousefpour
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/restart_app/android/.gitignore b/restart_app/android/.gitignore
new file mode 100644
index 0000000..c6cbe56
--- /dev/null
+++ b/restart_app/android/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/restart_app/android/build.gradle b/restart_app/android/build.gradle
new file mode 100644
index 0000000..5775252
--- /dev/null
+++ b/restart_app/android/build.gradle
@@ -0,0 +1,54 @@
+group 'gabrimatic.info.restart'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.7.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.3.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ if (project.android.hasProperty("namespace")) {
+ namespace 'gabrimatic.info.restart'
+ }
+
+ compileSdk 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ minSdk 16
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/restart_app/android/gradle.properties b/restart_app/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/restart_app/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/restart_app/android/gradle/wrapper/gradle-wrapper.properties b/restart_app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..00f9c2e
--- /dev/null
+++ b/restart_app/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Nov 21 13:39:27 CET 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/restart_app/android/settings.gradle b/restart_app/android/settings.gradle
new file mode 100644
index 0000000..ae057a2
--- /dev/null
+++ b/restart_app/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'restart'
diff --git a/restart_app/android/src/main/AndroidManifest.xml b/restart_app/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..be84c4f
--- /dev/null
+++ b/restart_app/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/restart_app/android/src/main/kotlin/gabrimatic/info/restart/RestartPlugin.kt b/restart_app/android/src/main/kotlin/gabrimatic/info/restart/RestartPlugin.kt
new file mode 100644
index 0000000..ae5ba31
--- /dev/null
+++ b/restart_app/android/src/main/kotlin/gabrimatic/info/restart/RestartPlugin.kt
@@ -0,0 +1,92 @@
+package gabrimatic.info.restart
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.NonNull
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+
+/**
+ * `RestartPlugin` class provides a method to restart a Flutter application in Android.
+ *
+ * It uses the Flutter platform channels to communicate with the Flutter code.
+ * Specifically, it uses a `MethodChannel` named 'restart' for this communication.
+ *
+ * The main functionality is provided by the `onMethodCall` method.
+ */
+class RestartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
+ private lateinit var context: Context
+ private lateinit var channel: MethodChannel
+ private var activity: Activity? = null
+
+ /**
+ * Called when the plugin is attached to the Flutter engine.
+ *
+ * It initializes the `context` with the application context and
+ * sets this plugin instance as the handler for method calls from Flutter.
+ */
+ override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ context = flutterPluginBinding.applicationContext
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "restart")
+ channel.setMethodCallHandler(this)
+ }
+
+ /**
+ * Handles method calls from the Flutter code.
+ *
+ * If the method call is 'restartApp', it restarts the app and sends a successful result.
+ * For any other method call, it sends a 'not implemented' result.
+ */
+ override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
+ if (call.method == "restartApp") {
+ restartApp()
+ result.success("ok")
+ } else {
+ result.notImplemented()
+ }
+ }
+
+ /**
+ * Called when the plugin is detached from the Flutter engine.
+ *
+ * It removes the handler for method calls from Flutter.
+ */
+ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ }
+
+ /**
+ * Restarts the application.
+ */
+ private fun restartApp() {
+ activity?.let { currentActivity ->
+ val intent =
+ currentActivity.packageManager.getLaunchIntentForPackage(currentActivity.packageName)
+ intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ currentActivity.startActivity(intent)
+ currentActivity.finishAffinity()
+ }
+ }
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ activity = null
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ }
+
+ override fun onDetachedFromActivity() {
+ activity = null
+ }
+}
\ No newline at end of file
diff --git a/restart_app/ios/.gitignore b/restart_app/ios/.gitignore
new file mode 100644
index 0000000..0c88507
--- /dev/null
+++ b/restart_app/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/restart_app/ios/Assets/.gitkeep b/restart_app/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/restart_app/ios/Classes/RestartAppPlugin.swift b/restart_app/ios/Classes/RestartAppPlugin.swift
new file mode 100644
index 0000000..afa431b
--- /dev/null
+++ b/restart_app/ios/Classes/RestartAppPlugin.swift
@@ -0,0 +1,25 @@
+import Flutter
+import UIKit
+
+public class RestartAppPlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let channel = FlutterMethodChannel(name: "restart", binaryMessenger: registrar.messenger())
+ let instance = RestartAppPlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ if call.method == "restartApp" {
+ DispatchQueue.main.async {
+ if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+ appDelegate.restartFlutterApp()
+ result("ok")
+ } else {
+ result(FlutterError(code: "APP_DELEGATE_NOT_FOUND", message: "Could not find AppDelegate", details: nil))
+ }
+ }
+ } else {
+ result(FlutterMethodNotImplemented)
+ }
+ }
+}
\ No newline at end of file
diff --git a/restart_app/ios/restart_app.podspec b/restart_app/ios/restart_app.podspec
new file mode 100644
index 0000000..756f7d9
--- /dev/null
+++ b/restart_app/ios/restart_app.podspec
@@ -0,0 +1,23 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint restart_app.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+ s.name = 'restart_app'
+ s.version = '0.0.1'
+ s.summary = 'A new Flutter project.'
+ s.description = <<-DESC
+A new Flutter project.
+ DESC
+ s.homepage = 'http://example.com'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Your Company' => 'email@example.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.dependency 'Flutter'
+ s.platform = :ios, '11.0'
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+ s.swift_version = '5.0'
+end
diff --git a/restart_app/lib/restart_app.dart b/restart_app/lib/restart_app.dart
new file mode 100644
index 0000000..9accaef
--- /dev/null
+++ b/restart_app/lib/restart_app.dart
@@ -0,0 +1,41 @@
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+
+/// `Restart` class provides a method to restart a Flutter application.
+///
+/// It uses the Flutter platform channels to communicate with the platform-specific code.
+/// Specifically, it uses a `MethodChannel` named 'restart' for this communication.
+///
+/// The main functionality is provided by the `restartApp` method.
+class Restart {
+ /// A private constant `MethodChannel`. This channel is used to communicate with the
+ /// platform-specific code to perform the restart operation.
+ static const MethodChannel _channel = const MethodChannel('restart');
+
+ /// Restarts the Flutter application.
+ ///
+ /// The `webOrigin` parameter is optional. If it's null, the method uses the `window.origin`
+ /// to get the site origin. This parameter should only be filled when your current origin
+ /// is different than the app's origin. It defaults to null.
+ ///
+ /// The `customMessage` parameter is optional. It allows customization of the notification
+ /// message displayed on iOS when restarting the app. If not provided, a default message
+ /// will be used.
+ ///
+ /// This method communicates with the platform-specific code to perform the restart operation,
+ /// and then checks the response. If the response is "ok", it returns true, signifying that
+ /// the restart operation was successful. Otherwise, it returns false.
+ static Future restartApp({
+ String? webOrigin,
+ String? notificationTitle,
+ String? notificationBody,
+ }) async {
+ final Map args = {
+ 'webOrigin': webOrigin,
+ 'notificationTitle': notificationTitle,
+ 'notificationBody': notificationBody,
+ };
+ return (await _channel.invokeMethod('restartApp', args)) == "ok";
+ }
+}
diff --git a/restart_app/lib/restart_web.dart b/restart_app/lib/restart_web.dart
new file mode 100644
index 0000000..a42735a
--- /dev/null
+++ b/restart_app/lib/restart_web.dart
@@ -0,0 +1,59 @@
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+// In order to *not* need this ignore, consider extracting the "web" version
+// of your plugin as a separate package, instead of inlining it in the same
+// package as the core of your plugin.
+// ignore: avoid_web_libraries_in_flutter
+import 'package:web/web.dart' as web show window;
+
+/// `RestartWeb` provides a web implementation of the `Restart` plugin.
+///
+/// It registers a `MethodChannel` named 'restart' for communication between the Flutter code
+/// and the platform-specific web code.
+///
+/// The main functionality is provided by the `restart` method.
+class RestartWeb {
+ /// Registers this plugin with the given `registrar`.
+ ///
+ /// This creates a `MethodChannel` named 'restart', and sets the method call handler to
+ /// this plugin's `handleMethodCall` method.
+ static void registerWith(Registrar registrar) {
+ final MethodChannel channel = MethodChannel(
+ 'restart',
+ const StandardMethodCodec(),
+ registrar,
+ );
+
+ final pluginInstance = RestartWeb();
+ channel.setMethodCallHandler(pluginInstance.handleMethodCall);
+ }
+
+ /// Handles method calls from the Flutter code.
+ ///
+ /// If the method call is 'restartApp', it calls the `restart` method with the given `webOrigin`.
+ /// Otherwise, it returns 'false' to signify that the method call was not recognized.
+ Future handleMethodCall(MethodCall call) async {
+ switch (call.method) {
+ case 'restartApp':
+ return restart(call.arguments as String?);
+ default:
+ return 'false';
+ }
+ }
+
+ /// Restarts the web app.
+ ///
+ /// The `webOrigin` parameter is optional. If it's null, the method uses the `window.origin`
+ /// to get the site origin. This parameter should only be filled when your current origin
+ /// is different than the app's origin. It defaults to null.
+ ///
+ /// This method replaces the current location with the given `webOrigin` (or `window.origin` if
+ /// `webOrigin` is null), effectively reloading the web app.
+ void restart(String? webOrigin) {
+ web.window.location.replace(
+ webOrigin ?? web.window.origin.toString(),
+ );
+ }
+}
diff --git a/restart_app/pubspec.yaml b/restart_app/pubspec.yaml
new file mode 100644
index 0000000..5e5610d
--- /dev/null
+++ b/restart_app/pubspec.yaml
@@ -0,0 +1,33 @@
+name: restart_app
+description: A Flutter plugin that helps you to restart the whole Flutter app with a single function call by using native APIs.
+version: 1.3.2
+repository: https://github.com/gabrimatic/restart_app
+issue_tracker: https://github.com/gabrimatic/restart_app/issues
+
+environment:
+ sdk: ^3.5.1
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ web: ^1.0.0
+ plugin_platform_interface: ^2.1.8
+ flutter_web_plugins:
+ sdk: flutter
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ plugin:
+ platforms:
+ android:
+ package: gabrimatic.info.restart
+ pluginClass: RestartPlugin
+ ios:
+ pluginClass: RestartAppPlugin
+ web:
+ pluginClass: RestartWeb
+ fileName: restart_web.dart
\ No newline at end of file